﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Noris.Schedule.Support.Sql;

namespace Noris.Schedule.Support.Green
{
    /// <summary>
    /// Strom tříd, vycházející z jednoho kořenu a mající různé větve ke koncovým prvkům.
    /// Na závěr dokáže vygenerovat SQL SELECT, který odpovídá danému stromu, 
    /// nebo dokáže svoje větve integrovat do daného selectu.
    /// Nové větve přidává na základě symbolické cesty, viz metoda AddBranch.
    /// </summary>
    public class ClassTree
    {
        #region Konstrukce, public property a metody
        /// <summary>
        /// Konstruktor
        /// </summary>
        public ClassTree()
        { }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="phraseFrom"></param>
        public ClassTree(SqlParsePhraseFrom phraseFrom)
        {
            this.PhraseFrom = phraseFrom;
        }
        /// <summary>
        /// Vyprázdní strom tříd.
        /// Po vyprázdnění je možno změnit frázi FROM, a zadávat nové větve metodou AddBranch.
        /// </summary>
        public void Clear()
        {
            this.RootItem = null;
            this._FinalList = null;
            this._Aliases = null;
        }
        /// <summary>
        /// <para>
        /// Metoda přidá další větev stromu, a vrátí korektně aliasovaný název koncového listu (atribut).
        /// Vstupem je symbolická cesta ve tvaru Stringu, kde jsou vypsány třídy a vztahy a koncový atribut:
        /// "C1151:R8926:C343:R101028:C22:L1520:C151:Master.typ_skladu".
        /// Plná cesta se dekóduje: začínat musí C (Class), následuje číslo třídy a dvojtečka.
        /// Pak může být uveden vztah R nebo L, kdy R značí vztah doprava (typicky na číselník) 
        /// nebo L = doleva (typicky na podřízené záznamy, které ukazují svým vztahem na číselník), následuje číslo vztahu.
        /// Po vztahu musí následovat C a číslo třídy.
        /// Po třídě může kromě vztahu následovat i Master/Entries/Subject/UdaMaster/UdaEntries.column atributu.
        /// </para>
        /// <para>
        /// Tato metoda přidá výsledný atribut nejen do stromu tříd, ale i do soupisu FinalColumnList, na rozdíl od metody AddBranchToTree().
        /// </para>
        /// </summary>
        /// <param name="fullTreePath"></param>
        /// <returns></returns>
        public IClassTreeColumn AddBranchToFinal(string fullTreePath)
        {
            ClassTreeItem last = this._AddBranchToTree(fullTreePath);
            this.FinalList.Add(last);
            return (IClassTreeColumn)last;
        }
        /// <summary>
        /// <para>
        /// Metoda přidá další větev stromu, a vrátí korektně aliasovaný název koncového listu (atribut).
        /// Vstupem je symbolická cesta ve tvaru Stringu, kde jsou vypsány třídy a vztahy a koncový atribut:
        /// "C1151:R8926:C343:R101028:C22:L1520:C151:Master.typ_skladu".
        /// Plná cesta se dekóduje: začínat musí C (Class), následuje číslo třídy a dvojtečka.
        /// Pak může být uveden vztah R nebo L, kdy R značí vztah doprava (typicky na číselník) 
        /// nebo L = doleva (typicky na podřízené záznamy, které ukazují svým vztahem na číselník), následuje číslo vztahu.
        /// Po vztahu musí následovat C a číslo třídy.
        /// Po třídě může kromě vztahu následovat i Master/Entries/Subject/UdaMaster/UdaEntries.column atributu.
        /// </para>
        /// <para>
        /// Tato metoda přidá výsledný atribut pouze do stromu tříd, ale nepřidává jej do soupisu FinalColumnList, na rozdíl od metody AddBranchToFinal().
        /// </para>
        /// </summary>
        /// <param name="fullTreePath"></param>
        /// <returns></returns>
        public IClassTreeColumn AddBranchToTree(string fullTreePath)
        {
            ClassTreeItem last = this._AddBranchToTree(fullTreePath);
            return (IClassTreeColumn)last;
        }
        /// <summary>
        /// Přidá danou cestu do stromu, ale ne do FinalList.
        /// Vrací finálový prvek.
        /// </summary>
        /// <param name="fullTreePath"></param>
        /// <returns></returns>
        private ClassTreeItem _AddBranchToTree(string fullTreePath)
        {
            ClassTreeBranch path = ClassTreeBranch.CreateFrom(fullTreePath);
            if (path == null) return null;
            ClassTreeItem last = this._AddBranch(path);
            return last;
        }
        /// <summary>
        /// Příznak, že strom je prázdný.
        /// Strom lze vyprázdnit metodou Clear.
        /// Pouze do prázdného stromu lze vložit PhraseFrom = opěrná fráze pro ukotvení stromu tříd.
        /// </summary>
        public bool IsEmpty { get { return (this.RootItem == null); } }
        /// <summary>
        /// Kořen stromu.
        /// Prvky stromu jsou svázané mezi sebou vztahem mezi jednotlivými prvky (RootItem.ItemUpList[xx].ItemUpList...).
        /// Prvky umístěné ve stromu obsahují realizované vazby, dané jejich charakterem 
        /// (na rozdíl od prvků vstupujících v nově přidávané věvi, která má charakter "deklarace").
        /// Přidávání prvků a jejich realizaci (konkretizaci vztahů) provádí metoda this.AddBranch().
        /// </summary>
        protected ClassTreeItem RootItem { get; private set; }
        /// <summary>
        /// Seznam koncových prvků vložených větví
        /// </summary>
        protected List<ClassTreeItem> FinalList
        {
            get
            {
                if (this._FinalList == null)
                    this._FinalList = new List<ClassTreeItem>();
                return this._FinalList;
            }
        }
        private List<ClassTreeItem> _FinalList;
        /// <summary>
        /// Fráze FROM, o kterou se strom může opírat.
        /// Frázi je možno vložit (nebo vynulovat) pouze tehdy, když je strom prázdný (IsEmpty), jinak dojde k chybě.
        /// </summary>
        public SqlParsePhraseFrom PhraseFrom
        {
            get { return this._PhraseFrom; }
            set
            {
                if (this.IsEmpty)
                {
                    this._PhraseFrom = value;
                    this.IsJoinToFromPhrase = false;           // na true se nastaví až při tom, kdy skutečně dojde k opření stromu o předanou FROM frázi.
                }
                else
                    throw new ClassTreeException("Do stromu tříd ClassTree nelze vložit frázi FROM, protože strom už je naplněn.");
            }
        }
        private SqlParsePhraseFrom _PhraseFrom;
        /// <summary>
        /// Příznak, že tento ClassTree pro svůj růst využil frázi FROM předaného SQL příkazu.
        /// </summary>
        public bool IsJoinToFromPhrase { get; set; }
        /// <summary>
        /// Režim tvorby aliasu pro sloupce, které se vkládají do selectu.
        /// Uplatní se i po vložení všech sloupců, zohlední se při tvorbě SqlSelect,
        /// a promítne se (okamžitě) i do hodnoty IClassTreeColumn.Alias.
        /// </summary>
        public ColumnAutoAliasMode ColumnAutoAlias { get; set; }
        #endregion
        #region Property které vytvářejí výstupní informace
        /// <summary>
        /// Obsahuje List všech finálových columnů (název sloupce včetně aliasu tabulky).
        /// </summary>
        public List<string> FinalColumnList
        {
            get
            {
                List<string> result = this.FinalList.ConvertAll(i => i.AttributeAliasedColumn);
                return result;
            }
        }
        /// <summary>
        /// Obsahuje string, který popisuje všechny finálové sloupce
        /// </summary>
        public string FinalColumns
        {
            get
            {
                StringBuilder sb = new StringBuilder();

                bool preComa = false;
                string line = "";
                string prevTable = "";
                foreach (ClassTreeItem item in this.FinalList)
                {
                    if (preComa)
                        line += ",";

                    string itemColumn = item.AttributeAliasedColumn;
                    string tableAlias = itemColumn.Substring(0, itemColumn.IndexOf(".")).ToLower();  // AttributeAliasedColumn má vždycky tabulku, tečku a název sloupce
                    bool tableChanged = (prevTable.Length > 0 && tableAlias != prevTable);           // Tento atribut je z jiné tabulky, než atributy předešlé!

                    string itemAlias = item.AttributeColumnAlias;
                    if (!String.IsNullOrEmpty(itemAlias))
                        itemColumn += " as " + itemAlias;

                    if (tableChanged || (line.Length > 0 && (line + itemColumn).Length > 65))
                    {
                        sb.AppendLine(line);
                        line = "";
                    }
                    line += " " + itemColumn;

                    preComa = true;
                    prevTable = tableAlias;
                }
                if (line.Length > 0)
                    sb.AppendLine(line);

                return sb.ToString();
            }
        }
        /// <summary>
        /// Obsahuje (vrátí) seznam všech tabulek, které tento ClassTree přidal nad rámec fráze FROM (předané explicitně do ClassTree).
        /// </summary>
        public List<string> AddedTableList
        {
            get
            {
                if (this.RootItem == null)
                    return new List<string>();
                return this.RootItem.GetAddedTables(true);
            }
        }
        /// <summary>
        /// Obsahuje (vrátí) string obsahující seznam všech tabulek, které tento ClassTree přidal nad rámec fráze FROM (předané explicitně do ClassTree).
        /// </summary>
        public string AddedTables
        {
            get
            {
                StringBuilder sb = new StringBuilder();
                List<string> tableList = this.AddedTableList;
                foreach (string table in tableList)
                    sb.AppendLine(table);
                return sb.ToString();
            }
        }
        /// <summary>
        /// Aktuální podoba příkazu SQL SELECT, který zajistí načtení zadaných dat z databáze.
        /// </summary>
        public string SqlSelect
        {
            get
            {
                StringBuilder sb = new StringBuilder();

                // SELECT:
                sb.AppendLine("SELECT ");

                // Sloupce:
                sb.Append(this.FinalColumns);

                // From fráze:
                if (this.PhraseFrom != null)
                    sb.AppendLine(this.PhraseFrom.ToString());

                // Tabulky:
                sb.Append(this.AddedTables);

                return sb.ToString();
            }
        }
        #endregion
        #region Tvorba stromu, vyrůstajícího z kořene, vznikající přidáváním dalších větví
        /// <summary>
        /// Do this stromu přidá další větev.
        /// Vrátí poslední prvek přidané větve (obsahuje požadovaný atribut).
        /// </summary>
        /// <param name="branch"></param>
        private ClassTreeItem _AddBranch(ClassTreeBranch branch)
        {
            if (branch.IsEmpty) return null;

            ClassTreeItem lastTreeItem = null;     // Prvek ve stromu, který je poslední odpovídající nově vkládané větvi
            ClassTreeItem joinBranchItem = null;   // Prvek ve větvi, který je první nenapojený (bude se napojovat na prvek lastTreeItem).
            if (this.RootItem == null)
            {   // Vkládám první větev = začnu vkládat od jejího Rootu, z něhož se stane Root celého stromu:
                joinBranchItem = branch.RootItem;
                // Zakořenění stromu:
                this._SetRoot(ref lastTreeItem, ref joinBranchItem);
            }
            else
            {   // Ve stromě už nějaké větve jsou, zkusíme ke stávajícímu stromu připojit novou větev - pokud má kousek cesty společný:
                this._PinBranch(branch, ref lastTreeItem, ref joinBranchItem);
            }
            
            // Postupné připojování dalších prvků z nové větve do stávajícího stromu
            if (lastTreeItem != null)
                lastTreeItem = _JoinBranch(lastTreeItem, joinBranchItem);

            return lastTreeItem;
        }
        /// <summary>
        /// Zakořenění stromu
        /// </summary>
        /// <param name="lastTreeItem"></param>
        /// <param name="joinBranchItem"></param>
        private void _SetRoot(ref ClassTreeItem lastTreeItem, ref ClassTreeItem joinBranchItem)
        {
            this.RootItem = joinBranchItem.CloneUnlinked();
            this.RootItem.OwnerClassTree = this;

            // Zakořenit root entitu buď do existující fráze FROM, anebo zařadit tabulku Master jako FROM:
            if (this.PhraseFrom != null)
            {
                this.AddAliasFrom();
                this.RootItem.FindTablesInPhraseFrom(this.PhraseFrom);
                // this.FindTablesInPhraseFrom();
                this.IsJoinToFromPhrase = true;
            }
            else
            {
                this.RootItem.AddTableAsFrom(ClassTableType.Master);
                this.IsJoinToFromPhrase = false;
            }

            // Co budeme napojovat dále:
            if (joinBranchItem.IsLast)
            {   // Root je poslední (naprosto netypické, protože poslední by měl být atribut, a Root je Class):
                lastTreeItem = null;
            }
            else
            {   // Budeme napojovat další prvek z větve:
                lastTreeItem = this.RootItem;                    // Poslední prvek stromu
                joinBranchItem = joinBranchItem.LinkUpFirst;     // Příští napojovaný prvek z větve
            }
        }
        /// <summary>
        /// Vyhledá v aktuálním stromu (počínaje Root) společnou cestu s dodanou větví (branch).
        /// </summary>
        /// <param name="branch"></param>
        /// <param name="lastTreeItem"></param>
        /// <param name="joinBranchItem"></param>
        private void _PinBranch(ClassTreeBranch branch, ref ClassTreeItem lastTreeItem, ref ClassTreeItem joinBranchItem)
        {
            // Ve stromu už existuje Root; dohledám si poslední synchronní prvek ve stromu (this) a v nové větvi (path), od něhož začnu napojovat novou větev do stromu:
            // O co jde: pokud mám strom (this) pro jednoduchost s jednou větví: "C1180:R9260:C1151:Master.status"
            //   a nyní přidávám další větev: "C1180:R9260:C1151:R6120:C343:Subject.reference_zaznamu",
            //   tak nově přidávaná větev se nemusí opírat o Root, ale najde si místo ve stávajícím stromu, kam až strom roste souhlasně s touto větví,
            //   a svoje přírůstky začne vkládat až od tohoto posledního společného místa.
            //  Jinými slovy, v příkladu projdeme společnou cestu: "C1180:R9260:C1151", kde ve stromu následuje "Master.status", a v nové větvi "R6120:C343:Subject.reference_zaznamu".
            //  Takže nová větev se přidá jako Up do prvku "C1151", přidá tam svůj prvek "R6120" a bude přidávat svoje další.
            //  Vskutku je o strom, kde další nové větve mohou vyrůstat kdekoli ze stávající struktury stromu.
            ClassTreeItem scanItem = branch.RootItem;
            ClassTreeItem treeItem = this.RootItem.FindNearestUpItem(scanItem.Key);
            if (treeItem == null)
                throw new ClassTreeException("Do existujícího stromu tříd nelze přidat další větev, jejíž Root není možno nalézt ve stávajícím stromu.");
            while (true)
            {   // Smyčka začíná vždy ve stavu, kdy scanItem a treeItem mají shodný klíč.
                // Prvek treeItem je záchytný bod na stromě (tedy je součástí stromu), o který se právě nyní můžeme opřít.
                // V této smyčce se pokusíme dohledat nejvyšší možný prvek stromu this, který je ještě shodný s přidávanou větví:

                // Pokud prvek přidávané větve je už poslední (nemá vyšší prvek): skončíme s aktuálním prvek stromu (treeItem), ten už je realizovaný:
                if (scanItem.IsLast)
                {
                    // Sem bych chodit neměl:
                    lastTreeItem = null;           // Nic dalšího se joinovat nemusí.
                    break;
                }

                // Prvek přidávané větve není poslední.
                // Pokud příští prvek (LinkUpFirst) je poslední, tak ten příští nebudeme synchronizovat, ale připojíme jej do stromu jako další nový prvek.
                // Důvod? 
                //   Vkládání nové větve (tato metoda) je volané proto, abych získal referenci na poslední prvek (=atribut).
                //   Někdy může volající chtít přidat tentýž prvek (zcela identickou cestu) znovu. To je jeho věc.
                //   Pak ale nové volání musí vrátit novou instanci posledního prvku (atributu), i když by byl shodný s prvek už dříve přidaným. 
                //   Kromě jiného bude mít atribut (db column) svůj alias, bude se načítat do nového sloupce v DataTable, atd.
                // Za tím účelem, pokud zde narazím na poslední prvek, tak ho nebudu synchronizovat, ale zajistím jeho nové napojení do stromu, do předešlého prvku:
                scanItem = scanItem.LinkUpFirst;
                if (scanItem.IsLast)
                {
                    lastTreeItem = treeItem;
                    joinBranchItem = scanItem;
                    break;
                }

                ClassTreeItem nextTreeItem = treeItem.LinkUpList.FirstOrDefault(i => i.Key == scanItem.Key);
                // Pokud ve stromě na současné pozici není žádný následující prvek s klíčem, který hledáme: musíme si svoje prvky připojit jako nové:
                if (nextTreeItem == null)
                {
                    lastTreeItem = treeItem;
                    joinBranchItem = scanItem;
                    break;
                }
                treeItem = nextTreeItem;
            }
        }
        /// <summary>
        /// K danému prvku stromu (lastTreeItem) připojí novou větev počínaje jejím prvkem (joinBranchItem).
        /// Dále připojí další prvky. Připojuje kopie (klony).
        /// </summary>
        /// <param name="lastTreeItem"></param>
        /// <param name="joinBranchItem"></param>
        /// <returns></returns>
        private static ClassTreeItem _JoinBranch(ClassTreeItem lastTreeItem, ClassTreeItem joinBranchItem)
        {
            ClassTreeItem branchItem = joinBranchItem;
            while (branchItem != null)
            {
                // Zkopíruji položku z dodané větve, a zapojím tuto kopii do stromu:
                ClassTreeItem newTreeItem = branchItem.CloneUnlinked();
                lastTreeItem.LinkUpList.Add(newTreeItem);
                newTreeItem.LinkDown = lastTreeItem;

                // Na vznikajícím stromě si přelezeme nahoru, jako na nový špruslík na žebříčku, který jsme před chvilkou připevnili:
                lastTreeItem = newTreeItem;
                // A ve vstupní větvi postoupíme na další prvek v napojované větvi:
                branchItem = (branchItem.IsLast ? null : branchItem.LinkUpFirst);
            }
            return lastTreeItem;
        }
        /// <summary>
        /// Metoda vrací true, pokud se daný sloupec vyskytuje v tabulce lcs.subjekty.
        /// </summary>
        /// <param name="columnName"></param>
        /// <returns></returns>
        internal bool IsColumnFromLcsSubjekty(string columnName)
        {
            if (String.IsNullOrEmpty(columnName))
                throw new ClassTreeException("Nelze určit příslušnost sloupce do tabulky subjektů, název sloupce není zadán.");
            Dictionary<string, DbColumnInfo> subjectDict = this._SubjectColumnDict;         // get accessor v případě potřeby vygeneruje tuto dictionary - načtením z repository Tables.
            return subjectDict.ContainsKey(columnName.Trim().ToLower());
        }
        /// <summary>
        /// Metoda se pokusí najít informace o databázovém sloupci daného jména z tabulky lcs.subjekty.
        /// </summary>
        /// <param name="columnName"></param>
        /// <param name="columnInfo"></param>
        /// <returns></returns>
        internal bool TryGetColumnFromLcsSubjekty(string columnName, out DbColumnInfo columnInfo)
        {
            if (String.IsNullOrEmpty(columnName))
                throw new ClassTreeException("Nelze vyhledat sloupec z tabulky subjektů, název sloupce není zadán.");
            Dictionary<string, DbColumnInfo> subjectDict = this._SubjectColumnDict;         // get accessor v případě potřeby vygeneruje tuto dictionary - načtením z repository Tables.
            return subjectDict.TryGetValue(columnName.Trim().ToLower(), out columnInfo);
        }
        /// <summary>
        /// Dictionary (cache) obsahující názvy sloupců tabulky lcs.subjekty
        /// </summary>
        private Dictionary<string, DbColumnInfo> _SubjectColumnDict
        {
            get
            {
                if (this.__SubjectColumnDict == null)
                {
                    Dictionary<string, DbColumnInfo> subjectDict = new Dictionary<string, DbColumnInfo>();
                    DbTableInfo tableInfo = Repository.Tables["lcs.subjekty"];
                    foreach (DbColumnInfo columnInfo in tableInfo.ColumnList)
                        subjectDict.Add(columnInfo.ColumnName.Trim().ToLower(), columnInfo);
                    this.__SubjectColumnDict = subjectDict;
                }
                return this.__SubjectColumnDict;
            }
        }
        private Dictionary<string, DbColumnInfo> __SubjectColumnDict;
        #endregion
        #region Mapování externě dodané fráze FROM a analýza jeho stromu tříd a tabulek, jako opěrný systém pro zdejší strom tříd
        /// <summary>
        /// Najde ve frázi FROM tabulky, které odpovídají této třídě, a založí je do svého přehledu Tables, aby se o ně mohl opírat strom tříd
        /// </summary>
        /// <param name="phraseFrom"></param>
        internal void FindTablesInPhraseFrom()
        {
            // Index obsahující Key = alias; Value = jednotlivé prvky (=třídy/vztahy) stromu tříd:
            List<PhraseFromEntity> entityList = new List<PhraseFromEntity>();
            
            // Zpracuji tabulky FROM ... JOIN do seznamu použitých entit (entita = sada tabulek, které jsou vzájemně provázané primárním klíčem, jedna třída):
            char[] charNotContain = new char[] { '+', '-', '(', ')', ' ' };   // Tyto znaky nemůže obsahovat jméno tabulky.
            for (int t = 0; t < this.PhraseFrom.FromTableList.Count; t++)
            {
                SqlParseFromTable table = this.PhraseFrom.FromTableList[t];
                string tableName = table.Source;
                // Pokud tabulka není zadána, nebo je to TEMP tabulka nebo SUBSELECT (obsahuje závorky, +-, mezeru): pak ji přeskočíme:
                if (String.IsNullOrEmpty(tableName)) continue;
                tableName = tableName.Trim().ToLower();
                if (tableName.StartsWith("#") || tableName.IndexOfAny(charNotContain) >= 0)
                    continue;

                if (tableName == "lcs.subjekty")
                    this.FindTableOneInPhraseFromSubj(table, entityList);
                else if (tableName == "lcs.vztahysubjektu")
                    this.FindTableOneInPhraseFromDRM(table, entityList);
                else if (tableName == "lcs.objektsubjekt")
                    this.FindTableOneInPhraseFromDRE(table, entityList);
                else
                    this.FindTableOneInPhraseFromClass(table, entityList);
            }

            // Zpracuji vztahy entit = určím parenty entit, doplním entity za statické vztahy:
            for (int e = 0; e < entityList.Count; e++)
            {
                PhraseFromEntity entity = entityList[e];
                if (e == 0)
                    this.FindTableOneInPhraseFromAddRoot(entityList[e]);
                else
                    this.FindTableOneInPhraseFromAddJoin(entityList, e);
            }

        }
        /// <summary>
        /// Přidá danou entitu do stromu tříd jako Root
        /// </summary>
        /// <param name="entity"></param>
        private void FindTableOneInPhraseFromAddRoot(PhraseFromEntity entity)
        {
            // this.RootItem = ClassTreeItem.Create();
        }
        /// <summary>
        /// Pokusí se najít a navázat danou entitu do stávajícího stromu tříd.
        /// Najde JOIN, určí odpovídající vztah a přes něj napojí entitu do stromu.
        /// </summary>
        /// <param name="entityList"></param>
        /// <param name="e"></param>
        private void FindTableOneInPhraseFromAddJoin(List<PhraseFromEntity> entityList, int e)
        {
            // throw new NotImplementedException();
        }
        /// <summary>
        /// Zpracuje tabulku lcs.subjekty. De facto ji zpracovává jako každou jinou tabulku třídy...
        /// </summary>
        /// <param name="table"></param>
        /// <param name="entityList"></param>
        private void FindTableOneInPhraseFromSubj(SqlParseFromTable table, List<PhraseFromEntity> entityList)
        {
            this.FindTableOneInPhraseFromClass(table, entityList);
        }
        /// <summary>
        /// Zpracuje tabulku lcs.vztahysubjektu
        /// </summary>
        /// <param name="table"></param>
        /// <param name="entityList"></param>
        private void FindTableOneInPhraseFromDRM(SqlParseFromTable table, List<PhraseFromEntity> entityList)
        {
            List<JoinOnExpressionEquals> equalsList = JoinOnExpressionEquals.CreateList(table, true);
            if (equalsList.Count >= 2)
            {   // Master dynamický vztah je typicky joinovaný na dva výrazy: záznam vlevo nebo vpravo, a číslo vztahu:
                JoinOnExpressionEquals eqRecord = equalsList.FirstOrDefault(eq => eq.HasOtherAlias && eq.HasOtherColumn && (eq.CurrentColumn == "cislo_subjektu" || eq.CurrentColumn == "cislo_vztaz_subjektu"));
                JoinOnExpressionEquals eqRelation = equalsList.FirstOrDefault(eq => eq.CurrentColumn == "cislo_vztahu" && !eq.HasOtherAlias && eq.HasOtherColumn && "0123456789".IndexOf(eq.OtherColumn[0]) >= 0);
                if (eqRecord != null && eqRelation != null)
                {   // Máme napojený dynamický vztah, kde je jednak určen vztah sloupců, i číslo vztahu:
                    ClassTreePathItemType entityType =
                        (eqRecord.CurrentColumn == "cislo_subjektu" ? ClassTreePathItemType.RelationLeftToRight:
                        (eqRecord.CurrentColumn == "cislo_vztaz_subjektu" ? ClassTreePathItemType.RelationRightToLeft : ClassTreePathItemType.None));
                    int relationNumber;
                    if (entityType != ClassTreePathItemType.None && Int32.TryParse(eqRelation.OtherColumn, out relationNumber))
                    {
                        if (Repository.Relations.Contains(relationNumber))
                        {
                            PhraseFromEntity entity = new PhraseFromEntity();
                            entity.RelationNumber = relationNumber;
                            entity.EntityType = entityType;
                            entity.JoinDownMaster = eqRecord;
                            entity.Tables.Add(table);
                            entityList.Add(entity);
                        }
                    }
                }
            }
        }
        /// <summary>
        /// Zpracuje tabulku lcs.objektsubjekt
        /// </summary>
        /// <param name="table"></param>
        /// <param name="entityList"></param>
        private void FindTableOneInPhraseFromDRE(SqlParseFromTable table, List<PhraseFromEntity> entityList)
        {
            List<JoinOnExpressionEquals> equalsList = JoinOnExpressionEquals.CreateList(table, true);
            if (equalsList.Count >= 2)
            {   // Master dynamický vztah je typicky joinovaný na dva výrazy: záznam vlevo nebo vpravo, a číslo vztahu:
                JoinOnExpressionEquals eqMasterRecord = equalsList.FirstOrDefault(eq => eq.HasOtherAlias && eq.HasOtherColumn && (eq.CurrentColumn == "cislo_dokladu" || eq.CurrentColumn == "cislo_subjektu"));
                JoinOnExpressionEquals eqEntriesRecord = equalsList.FirstOrDefault(eq => eq.HasOtherAlias && eq.HasOtherColumn && eq.CurrentColumn == "cislo_objektu");
                JoinOnExpressionEquals eqRelation = equalsList.FirstOrDefault(eq => eq.CurrentColumn == "cislo_vztahu" && !eq.HasOtherAlias && eq.HasOtherColumn && "0123456789".IndexOf(eq.OtherColumn[0]) >= 0);
                if (eqMasterRecord != null && eqRelation != null)
                {   // Máme napojený dynamický vztah, kde je jednak určen vztah sloupců, i číslo vztahu:
                    ClassTreePathItemType entityType =
                        (eqMasterRecord.CurrentColumn == "cislo_dokladu" ? ClassTreePathItemType.RelationLeftToRight :
                        (eqMasterRecord.CurrentColumn == "cislo_subjektu" ? ClassTreePathItemType.RelationRightToLeft : ClassTreePathItemType.None));
                    int relationNumber;
                    if (entityType != ClassTreePathItemType.None && Int32.TryParse(eqRelation.OtherColumn, out relationNumber))
                    {
                        if (Repository.Relations.Contains(relationNumber))
                        {
                            PhraseFromEntity entity = new PhraseFromEntity();
                            entity.RelationNumber = relationNumber;
                            entity.EntityType = entityType;
                            entity.JoinDownMaster = eqMasterRecord;
                            entity.JoinDownEntries = eqEntriesRecord;
                            entity.Tables.Add(table);
                            entityList.Add(entity);
                        }
                    }
                }
            }
        }
        /// <summary>
        /// Zpracuje tabulku obecnou (tabulka třídy)
        /// </summary>
        /// <param name="table"></param>
        /// <param name="entityList"></param>
        private void FindTableOneInPhraseFromClass(SqlParseFromTable table, List<PhraseFromEntity> entityList)
        {
            // Pokud tabulka tohoto názvu neexistuje v SQL databázi, taky ji přeskočíme:
            string tableName = table.Source.Trim().ToLower();
            DbTableInfo tableInfo = Repository.Tables[tableName];
            if (tableInfo == null) return;

            // Výraz JOIN ON:
            List<JoinOnExpressionEquals> equalsList = JoinOnExpressionEquals.CreateList(table, false);

            // Entita, kam tabulku přidáme:
            PhraseFromEntity entity = null;
            if (entityList.Count > 0)
            {   // Pokud už máme nějaké entity, zjistíme zda aktuální tabulka není JOINována k jiné tabulce uvnitř entity:
                foreach (PhraseFromEntity ent in entityList)
                {
                    if (ent.IsTablePrimaryJoined(equalsList))
                    {
                        entity = ent;
                        break;
                    }
                }
            }

            // Pokud dosud nemáme entitu, ke které by tato tabulka byla joinována přes PrimaryKey, založíme pro tuto tabulku novou entitu:
            if (entity == null)
            {
                // Zjistíme, zda v dosavadních entitách existuje tabulka s aliasem, na který se odvolává JOIN ON výraz:
                JoinOnExpressionEquals joinDownMaster = null;
                JoinOnExpressionEquals joinDownEntries = null;
                foreach (PhraseFromEntity ent in entityList)
                {
                    foreach (SqlParseFromTable tbl in ent.Tables)
                    {
                        JoinOnExpressionEquals f = equalsList.FirstOrDefault(eq => String.Equals(eq.OtherAlias, tbl.AliasSource));
                        if (f != null)
                        {
                            joinDownMaster = f;
                            break;
                        }
                    }
                    if (joinDownMaster != null) break;
                }
                entity = new PhraseFromEntity();
                entity.JoinDownMaster = joinDownMaster;
                entity.JoinDownEntries =  joinDownEntries;
                entityList.Add(entity);
            }
            if (entity != null)
                entity.AddTable(table);
        }
        /// <summary>
        /// Třída, do níž se shromažďují informace o jedné třídě / o jednom vztahu 
        /// z fráze FROM při její analýze.
        /// </summary>
        protected class PhraseFromEntity
        {
            internal PhraseFromEntity()
            {
                this.Tables = new List<SqlParseFromTable>();
                this.IdentityColumnMaster = null;
                this.IdentityColumnEntries = null;
                this.CurrentClassList = null;
            }
            public override string ToString()
            {
                string text = "Entity: " + this.EntityType.ToString() + "; ";
                if (this.IsRelation)
                    text += "Relation="  + this.RelationInfo.ToString() + "; ";
                else if (this.IsClass)
                    text += "Class count="  + this.CurrentClassList.Count.ToString() + "; ";
                text += "TablesCount=" + this.Tables.Count.ToString();
                return text;
            }
            internal ClassTreePathItemType EntityType { get; set; }
            /// <summary>
            /// Je v této entitě uložena třída?
            /// </summary>
            internal bool IsClass { get { return (this.EntityType == ClassTreePathItemType.Class); } }
            /// <summary>
            /// Je v této entitě uložen vztah?
            /// </summary>
            internal bool IsRelation { get { return (this.EntityType == ClassTreePathItemType.RelationLeftToRight || this.EntityType == ClassTreePathItemType.RelationRightToLeft); } }
            /// <summary>
            /// Číslo vztahu
            /// </summary>
            internal int RelationNumber { get; set; }
            /// <summary>
            /// Data o vztahu
            /// </summary>
            internal RelationInfo RelationInfo { get { return Repository.Relations[this.RelationNumber]; } }
            /// <summary>
            /// Výraz JOIN ON pro sloupec MASTER, kterým je tato entita napojena dolů na stávající tabulky fráze FROM
            /// </summary>
            internal JoinOnExpressionEquals JoinDownMaster { get; set; }
            /// <summary>
            /// Výraz JOIN ON pro sloupec ENTRIES, kterým je tato entita napojena dolů na stávající tabulky fráze FROM
            /// </summary>
            internal JoinOnExpressionEquals JoinDownEntries { get; set; }

            /// <summary>
            /// Soupis tabulek, které tato entita obsahuje
            /// </summary>
            internal List<SqlParseFromTable> Tables { get; private set; }
            /// <summary>
            /// Název sloupce Identity Master
            /// </summary>
            internal string IdentityColumnMaster { get; set; }
            /// <summary>
            /// Název sloupce Identity Entries
            /// </summary>
            internal string IdentityColumnEntries { get; set; }
            /// <summary>
            /// Třídy, které mohou v této entitě být umístěny
            /// </summary>
            internal List<ClassInfo> CurrentClassList { get; set; }
            /// <summary>
            /// Přidá do sebe další tabulku.
            /// V případě potřeby podle ní detekuje třídy a primární klíče.
            /// </summary>
            /// <param name="table"></param>
            internal void AddTable(SqlParseFromTable table)
            {
                this.Tables.Add(table);

                string tableName = table.Source.Trim().ToLower();
                if (tableName == "lcs.subjekty")
                {
                    if (this.IdentityColumnMaster == null)
                        this.IdentityColumnMaster = "cislo_subjektu";
                }
                else if (this.CurrentClassList == null)
                    this.DetectClassesAndPrimaryColumns(tableName);
            }
            /// <summary>
            /// Detekuje třídy odpovídající tabulce, a primární klíče tabulek Master a Entries.
            /// </summary>
            /// <param name="tableName"></param>
            private void DetectClassesAndPrimaryColumns(string tableName)
            {
                DbTableInfo tableInfo = Repository.Tables[tableName];
                if (tableInfo != null)
                {
                    List<ClassInfo> classList = tableInfo.ClassList;
                    if (classList != null && classList.Count > 0)
                    {
                        this.CurrentClassList = new List<ClassInfo>(classList);
                        ClassInfo classInfo = classList[0];  // Pokud tabulka patří do více tříd, prozatím předpokládám že tyto třídy mají stejný charakter.
                        this.IdentityColumnMaster = classInfo.GetIdentityColumn(ClassTableType.Master).Trim().ToLower();
                        this.IdentityColumnEntries = (classInfo.HasEntries ? classInfo.GetIdentityColumn(ClassTableType.Entries).Trim().ToLower() : null);
                    }
                }
            }
            /// <summary>
            /// Zjistí, zda daná tabulka je JOINována k některé z tabulek, které jsou již součástí této entity.
            /// </summary>
            /// <param name="table"></param>
            /// <returns></returns>
            internal bool IsTablePrimaryJoined(List<JoinOnExpressionEquals> equalsList)
            {
                foreach (JoinOnExpressionEquals eq in equalsList)
                {   // Projdu si JOIN ON výrazy, jejich části kde je na obou stranách rovnítka nějaká tabulka, 
                    // kde Current je tabulka z JOIN, a Other je tabulka k níž se JOINuje:
                    if (eq.EqualColumns && 
                        this.IsMyIdentityColumn(eq.CurrentColumn) && 
                        this.Tables.Exists(t => t.AliasSource == eq.OtherAlias))
                        // Tento výraz má na obou stranách rovnítka shodný sloupec, 
                        return true;
                }
                return false;
            }
            /// <summary>
            /// Vrací true, pokud daný sloupec (holý název) je jeden z mých IdentityColumnů
            /// </summary>
            /// <param name="column"></param>
            /// <returns></returns>
            private bool IsMyIdentityColumn(string column)
            {
                return ((this.IdentityColumnMaster != null && column == this.IdentityColumnMaster) ||
                        (this.IdentityColumnEntries != null && column == this.IdentityColumnEntries));
            }
        }
        /// <summary>
        /// třída, která obsahuje výraz na levé a pravé straně rovnítka, výraz rozdělený na tabulku a na alias.
        /// </summary>
        protected class JoinOnExpressionEquals
        {
            /// <summary>
            /// Vrátí seznam vztahů z JOIN ON výrazu, 
            /// kde na straně JoinOnExpressionEquals.Current je daná tabulka, 
            /// a na straně JoinOnExpressionEquals.Other je tabulka k níž se aktuální tabulka JOINuje 
            /// (alias tabulky již zapojené do FROM fráze).
            /// </summary>
            /// <param name="table"></param>
            /// <param name="enableOtherAll">Povolit na druhé straně rovnice cokoliv?  false = jen tabulka.sloupec / true = cokoliv</param>
            /// <returns></returns>
            public static List<JoinOnExpressionEquals> CreateList(SqlParseFromTable table, bool enableOtherAll)
            {
                List<JoinOnExpressionEquals> result = new List<JoinOnExpressionEquals>();
                string tableAlias = table.AliasSource;
                List<SqlParseItem> itemList = table.OnExpressions;
                for (int i = 1; i < (itemList.Count - 1); i++)
                {
                    if (itemList[i].Text == "=")
                    {
                        JoinOnExpressionEquals equals = JoinOnExpressionEquals.Create(itemList[i - 1].Text, itemList[i + 1].Text, tableAlias, enableOtherAll);
                        if (equals != null)
                            result.Add(equals);
                    }
                }
                return result;
            }
            /// <summary>
            /// 
            /// </summary>
            /// <param name="left"></param>
            /// <param name="right"></param>
            /// <param name="tableAlias"></param>
            /// <param name="enableOtherAll">Povolit na druhé straně rovnice cokoliv?  false = jen tabulka.sloupec / true = cokoliv</param>
            /// <returns></returns>
            public static JoinOnExpressionEquals Create(string left, string right, string tableAlias, bool enableOtherAll)
            {
                string leftAlias, leftColumn, rightAlias, rightColumn;
                if (DbTableInfo.SplitFullDbColumn(left, enableOtherAll, out leftAlias, out leftColumn) &&
                    DbTableInfo.SplitFullDbColumn(right, enableOtherAll, out rightAlias, out rightColumn))
                {
                    if (String.Equals(leftAlias, tableAlias, StringComparison.InvariantCultureIgnoreCase))
                        return new JoinOnExpressionEquals(leftAlias, leftColumn, rightAlias, rightColumn);
                    if (String.Equals(rightAlias, tableAlias, StringComparison.InvariantCultureIgnoreCase))
                        return new JoinOnExpressionEquals(rightAlias, rightColumn, leftAlias, leftColumn);
                }
                return null;
            }
            private JoinOnExpressionEquals(string currentAlias, string currentColumn, string otherAlias, string otherColumn)
            {
                this.CurrentAlias = currentAlias;
                this.CurrentColumn = currentColumn;
                this.OtherAlias = otherAlias;
                this.OtherColumn = otherColumn;
            }
            /// <summary>
            /// Vizualizace
            /// </summary>
            /// <returns></returns>
            public override string ToString()
            {
                return this.CurrentText + " = " + this.OtherText;
            }
            internal string CurrentAlias { get; private set; }
            internal string CurrentColumn { get; private set; }
            internal string CurrentText { get { return _MergeStrings(this.CurrentAlias, this.CurrentColumn); } }
            internal string OtherAlias { get; private set; }
            internal string OtherColumn { get; private set; }
            internal string OtherText { get { return _MergeStrings(this.OtherAlias, this.OtherColumn); } }
            private string _MergeStrings(params string[] texts)
            {
                string result = "";
                foreach (string text in texts)
                {
                    if (!String.IsNullOrEmpty(text))
                        result += (result.Length == 0 ? "" : ".") + text;
                }
                return result;
            }
            /// <summary>
            /// true, pokud CurrentColumn == OtherColumn
            /// </summary>
            internal bool EqualColumns { get { return (String.Equals(this.CurrentColumn, this.OtherColumn, StringComparison.InvariantCultureIgnoreCase)); } }
            /// <summary>
            /// true, pokud OtherAlias není prázdné
            /// </summary>
            internal bool HasOtherAlias { get { return !String.IsNullOrEmpty(this.OtherAlias); } }
            /// <summary>
            /// true, pokud OtherColumn není prázdné
            /// </summary>
            internal bool HasOtherColumn { get { return !String.IsNullOrEmpty(this.OtherColumn); } }
        }
        #endregion
        #region Podpora pro tvorbu aliasů
        /// <summary>
        /// Prefix pro aliasy tabulek. Implicitní prefix je "ct".
        /// Prefix je povoleno libovolně měnit.
        /// Prefix se zohledňuje v době, kdy se získává plný název koncového atributu, protože v té době dochází k zařazování potřebných tabulek do FROM/JOIN.
        /// V době přidávání větví (metoda AddBranch()) prefix není zapotřebí.
        /// </summary>
        public string AliasPrefix { get; set; }
        /// <summary>
        /// Vrátí alias pro danou tabulku.
        /// Alias si zapamatuje a příště pro tentýž vstup vrátí jiný, tak aby se jeden alias nikdy neopakoval.
        /// </summary>
        /// <param name="key"></param>
        /// <param name="tableType"></param>
        /// <returns></returns>
        internal string CreateAlias(string key, ClassTableType tableType)
        {
            string prefix = (String.IsNullOrEmpty(this.AliasPrefix) ? "ct" : this.AliasPrefix.Trim());
            if (!String.IsNullOrEmpty(key))
                prefix += "_" + key.Trim();                    // "ct_r9260"
            string suffix = _GetTableTypeSuffix(tableType);    // "ct_r9260e"

            Dictionary<string, bool> aliases = this.Aliases; // non-duplicity
            string alias = null;
            int n = 0;
            while (true)
            {
                alias = (prefix + suffix + (n == 0 ? "" : "_" + n.ToString())).Trim().ToLower();
                if (!aliases.ContainsKey(alias))
                    break;
                n++;
            }
            aliases.Add(alias, true);
            return alias;
        }
        /// <summary>
        /// Přidá do svého indexu aliasů ty aliasy, které jsou použity ve frázi FROM.
        /// Přidává všechny aliasy, nezkoumá tabulky.
        /// Důvod: abychom se těmto aliasům vyhnuli při přidělování našich aliasů.
        /// </summary>
        private void AddAliasFrom()
        {
            Dictionary<string, bool> aliases = this.Aliases;
            aliases.Clear();
            foreach (SqlParseFromTable table in this.PhraseFrom.FromTableList)
            {
                string alias = table.AliasSource;
                if (!String.IsNullOrEmpty(alias))
                {
                    if (aliases.ContainsKey(alias))
                        throw new ClassTreeException("Fráze FROM, o kterou se opírá ClassTree, obsahuje duplicitní tabulku / alias: \"" + alias + "\".");
                    aliases.Add(alias, false);
                }
            }
        }
        /// <summary>
        /// Index použitých aliasů.
        /// Jsou nastřádány z fráze From a postupně doplňován o aliasy přidělené zdejším tabulkám.
        /// Zajistí, že se alias nebude opakovat.
        /// Jak hodnota se ukládá bool, který vyjadřuje, zda 
        /// (true): uvedený alias pochází od zde vytvořené tabulky, tedy bude se exportovat metodou GetTables(), 
        /// anebo (false) zda alias pochází z externí fráze FROM, a exportovat se nebude.
        /// </summary>
        internal Dictionary<string, bool> Aliases
        {
            get
            {
                if (this._Aliases == null)
                    this._Aliases = new Dictionary<string, bool>();
                return this._Aliases;
            }
        }
        private Dictionary<string, bool> _Aliases;
        /// <summary>
        /// Vrátí krátký string odpovídající typu tabulky (S, M, E, UM, UE, R, X).
        /// Používá se v generítoru aliasů zdejších tabulek.
        /// </summary>
        /// <param name="tableType"></param>
        /// <returns></returns>
        private static string _GetTableTypeSuffix(ClassTableType tableType)
        {
            switch (tableType)
            {
                case ClassTableType.None: return "R";
                case ClassTableType.Subjekt: return "S";
                case ClassTableType.Master: return "M";
                case ClassTableType.Entries: return "E";
                case ClassTableType.UdaMaster: return "UM";
                case ClassTableType.UdaEntries: return "UE";
            }
            return "X";
        }
        #endregion
        #region class ClassTreeBranch : Třída, které v sobě nese jednu větev stromu tříd (od Rootu až k finálnímu atributu), jde v podstatě o přímku bez větvení.
        /// <summary>
        /// ClassTreeBranch : Třída, které v sobě nese jednu větev stromu tříd (od Rootu až k finálnímu atributu), jde v podstatě o přímku bez větvení.
        /// </summary>
        protected class ClassTreeBranch
        {
            private ClassTreeBranch(string fullTreePath)
            {
                this.FullTreePath = fullTreePath;
                this.Path = new List<ClassTreeItem>();
            }
            /// <summary>
            /// Vizualizace
            /// </summary>
            /// <returns></returns>
            public override string ToString()
            {
                return this.FullTreePath;
            }
            /// <summary>
            /// Sestaví a vrátí zadanou cestu fullTreePath převedenou na řadu položek
            /// </summary>
            /// <param name="fullTreePath"></param>
            /// <returns></returns>
            public static ClassTreeBranch CreateFrom(string fullTreePath)
            {
                if (String.IsNullOrEmpty(fullTreePath))
                    throw new ClassTreeException("Nelze sestavit ClassTreePath, zadaná cesta (fullTreePath) je prázdná.");

                ClassTreeBranch path = new ClassTreeBranch(fullTreePath);
                string crl = ClassTree.TREEPATH_CLASS.ToString() + ClassTree.TREEPATH_REL_LEFT_TO_RIGHT.ToString() + ClassTree.TREEPATH_REL_RIGHT_TO_LEFT.ToString();
                string[] items = fullTreePath
                    .Replace(ClassTree.TREEPATH_SEP_ATTR_TABLE, ClassTree.TREEPATH_SEP_ITEM)
                    .Split(ClassTree.TREEPATH_SEP_ITEM);
                for (int i = 0; i < items.Length; i++)
                {
                    string item = items[i].Trim();
                    if (item.Length <= 2)
                        throw new ClassTreeException("Nelze sestavit ClassTreePath, prvek cesty (fullTreePath = " + fullTreePath + ") na indexu " + i.ToString() + " je prázdný.");

                    ClassTreeItem pathItem = null;
                    int itemNumber = 0;
                    char first = item[0];
                    if (crl.IndexOf(first) >= 0 && Int32.TryParse(item.Substring(1), out itemNumber) && itemNumber > 0)
                    {
                        ClassInfo classInfo = null;
                        RelationInfo relationInfo = null;
                        switch (first)
                        {
                            case ClassTree.TREEPATH_CLASS:
                                classInfo = Repository.Classes[itemNumber];
                                if (classInfo != null)
                                    pathItem = ClassTreeItem.Create(path, item, ClassTreePathItemType.Class, itemNumber, null);
                                break;
                            case ClassTree.TREEPATH_REL_LEFT_TO_RIGHT:
                                relationInfo = Repository.Relations[itemNumber];
                                if (relationInfo != null)
                                    pathItem = ClassTreeItem.Create(path, item, ClassTreePathItemType.RelationLeftToRight, itemNumber, null);
                                break;
                            case ClassTree.TREEPATH_REL_RIGHT_TO_LEFT:
                                relationInfo = Repository.Relations[itemNumber];
                                if (relationInfo != null)
                                    pathItem = ClassTreeItem.Create(path, item, ClassTreePathItemType.RelationRightToLeft, itemNumber, null);
                                break;
                        }
                    }
                    else
	                {
                        int dotIndex = item.IndexOf(".");
                        if (dotIndex > 0 && dotIndex < (item.Length - 1))
                        {
                            string tableType = item.Substring(0, dotIndex).Trim();
                            string attributeName = item.Substring(dotIndex + 1).Trim();
                            if (String.IsNullOrEmpty(tableType) || String.IsNullOrEmpty(attributeName))
                                throw new ClassTreeException("Nelze sestavit ClassTreePath, prvek cesty (fullTreePath = " + fullTreePath + ") na indexu " + i.ToString() + " vypadá jako atribut, ale neobsahuje druh tabulky nebo sloupec atributu.");

                            switch (tableType)
                            {
                                case ClassTree.TREEPATH_TABLE_SUBJECT:
                                    pathItem = ClassTreeItem.Create(path, item, ClassTreePathItemType.AttributeSubject, 0, attributeName);
                                    break;
                                case ClassTree.TREEPATH_TABLE_MASTER:
                                case ClassTree.TREEPATH_TABLE_RECORDNUMBER:
                                    pathItem = ClassTreeItem.Create(path, item, ClassTreePathItemType.AttributeMaster, 0, attributeName);
                                    break;
                                case ClassTree.TREEPATH_TABLE_MASTER_UDA:
                                    pathItem = ClassTreeItem.Create(path, item, ClassTreePathItemType.AttributeUdaMaster, 0, attributeName);
                                    break;
                                case ClassTree.TREEPATH_TABLE_ENTRIES:
                                    pathItem = ClassTreeItem.Create(path, item, ClassTreePathItemType.AttributeEntries, 0, attributeName);
                                    break;
                                case ClassTree.TREEPATH_TABLE_ENTRIES_UDA:
                                    pathItem = ClassTreeItem.Create(path, item, ClassTreePathItemType.AttributeUdaEntries, 0, attributeName);
                                    break;
                                default:
                                    throw new ClassTreeException("Nelze sestavit ClassTreePath, prvek cesty (fullTreePath = " + fullTreePath + ") na indexu " + i.ToString() + " vypadá jako atribut, ale neobsahuje platný druh tabulky (" + tableType + ").");
                            }
                        }
	                }
                    
                    if (pathItem == null)
                        throw new ClassTreeException("Nelze sestavit ClassTreePath, prvek cesty (fullTreePath = " + fullTreePath + ") na indexu " + i.ToString() + " = " + item + " neodpovídá žádnému očekávanému typu.");

                    path.Path.Add(pathItem);
                }
                return path;
            }
            /// <summary>
            /// Vstupní textová reprezentace cesty
            /// </summary>
            public string FullTreePath { get; private set; }
            /// <summary>
            /// Kořenový prvek (Path[0]).
            /// Pokud je Path prázdná (IsEmpty), pak je zde null.
            /// </summary>
            public ClassTreeItem RootItem { get { return (this.IsEmpty ? null : this.Path[0]); } }
            /// <summary>
            /// Poslední prvek (Path[Count - 1]).
            /// Pokud je Path prázdná (IsEmpty), pak je zde null.
            /// </summary>
            public ClassTreeItem LastItem { get { return (this.IsEmpty ? null : this.Path[this.Path.Count - 1]); } }
            /// <summary>
            /// Příznak, že Path je prázdná (this.Path.Count == 0).
            /// </summary>
            public bool IsEmpty { get { return (this.Path.Count == 0); } }
            /// <summary>
            /// Jednotlivé prvky cesty
            /// </summary>
            public List<ClassTreeItem> Path { get; private set; }
        }
        #endregion
        #region class ClassTreeItem : Jednotlivé položky stromu tříd
        /// <summary>
        /// ClassTreeItem : Jednotlivé položky stromu tříd
        /// </summary>
        protected class ClassTreeItem : IClassTreeColumn
        {
            #region Konstrukce
            /// <summary>
            /// Privátní konstruktor
            /// </summary>
            /// <param name="parent"></param>
            private ClassTreeItem(ClassTreeBranch parent, string key)
            {
                this.LinkUpList = new List<ClassTreeItem>();
                this.Key = key;

                // Zřetězení prvků:
                if (!parent.IsEmpty)
                {
                    this.LinkDown = parent.LastItem;
                    this.LinkDown.LinkUpList.Add(this);
                }
            }
            /// <summary>
            /// Generátor objektu
            /// </summary>
            /// <param name="parent"></param>
            /// <param name="itemType"></param>
            /// <param name="itemNumber"></param>
            /// <param name="dbColumnName"></param>
            /// <returns></returns>
            internal static ClassTreeItem Create(ClassTreeBranch parent, string key, ClassTreePathItemType itemType, int itemNumber, string dbColumnName)
            {
                ClassTreeItem item = new ClassTreeItem(parent, key);
                item.ItemType = itemType;
                item.ItemNumber = itemNumber;
                item.DbAttributeColumnName = dbColumnName;
                return item;
            }
            /// <summary>
            /// Vizualizace
            /// </summary>
            /// <returns></returns>
            public override string ToString()
            {
                return this.Text;
            }
            /// <summary>
            /// Popisný text prvku
            /// </summary>
            public string Text
            {
                get
                {
                    string text = "";
                    if (this.IsClass)
                        text = this.ClassInfo.ToString();
                    else if (this.IsRelation)
                        text = this.ItemType.ToString() + ": " + this.RelationInfo.CisloVztahu.ToString() + " - " + this.RelationInfo.NameFromSide(this.RelationSide, true);
                    else if (this.IsAttribute)
                        text = "Attribute: " + this.AttributeTableType.ToString() + "." + this.DbAttributeColumnName;
                    return text;
                }
            }
            /// <summary>
            /// Vrátí klon sebe sama, ale odpojený od svých sousedů.
            /// </summary>
            /// <returns></returns>
            internal ClassTreeItem CloneUnlinked()
            {
                ClassTreeItem clone = (ClassTreeItem)this.MemberwiseClone();
                clone.LinkDown = null;
                clone.LinkUpList = new List<ClassTreeItem>();
                return clone;
            }
            #endregion
            #region Ukotvení Tree, Down / Up
            /// <summary>
            /// Vlastník = strom tříd.
            /// Běžně se reference na ClassTree ukládá pouze do Root prvku, ostatní prvky dohledají referenci na OwnerClassTree (pokud ji nemají samy) pomocí LinkDown.OwnerClassTree.
            /// </summary>
            public ClassTree OwnerClassTree
            {
                get
                {
                    if (this._OwnerClassTree != null)
                        return this._OwnerClassTree;
                    if (this.LinkDown != null)
                        return this.LinkDown.OwnerClassTree;
                    return null;
                }
                set { this._OwnerClassTree = value; }
            }
            private ClassTree _OwnerClassTree;
            /// <summary>
            /// true, pokud tento prvek je Root ve své linii (this.LinkDown == null)
            /// </summary>
            public bool IsRoot { get { return (this.LinkDown == null); } }
            /// <summary>
            /// Odkaz na prvek předchozí, směrem k Root.
            /// Root prvek zde má null, ostatní mají referenci na nejbližší prvek směrem k Rootu.
            /// </summary>
            public ClassTreeItem LinkDown { get; set; }
            /// <summary>
            /// true, pokud tento prvek je Last ve své linii (this.LinkUpList.Count == 0)
            /// </summary>
            public bool IsLast { get { return (this.LinkUpList.Count == 0); } }
            /// <summary>
            /// Odkaz na první následující prvek, směrem k Attribute.
            /// Má význam v roli Path, kdy prvek Up je nejvýše jeden.
            /// Fyzicky se čte z this.LinkUpList[0].
            /// </summary>
            public ClassTreeItem LinkUpFirst { get { return (this.IsLast ? null : this.LinkUpList[0]); } }
            /// <summary>
            /// Odkaz na následující prvky, směrem k Attribute.
            /// Attribute prvek zde má null, ostatní mají referenci na nejbližší prvek směrem k Attribute.
            /// </summary>
            public List<ClassTreeItem> LinkUpList { get; set; }
            /// <summary>
            /// Najde a vrátí nejbližší prvek (this nebo vyšší), jehož klíč je stejný jako daný klíč.
            /// Prohledává v rozšiřujících se vlnách nahoru (do šířky), nikoli rekurzivně jednotlivé prvky (do dálky).
            /// </summary>
            /// <param name="key">Hledaný klíč</param>
            /// <returns></returns>
            internal ClassTreeItem FindNearestUpItem(string key)
            {
                if (this.Key == key) return this;
                Queue<ClassTreeItem> scanItems = new Queue<ClassTreeItem>();
                scanItems.Enqueue(this);
                while (scanItems.Count > 0)
                {
                    ClassTreeItem scanItem = scanItems.Dequeue();
                    foreach (ClassTreeItem nextItem in scanItem.LinkUpList)
                    {
                        if (nextItem.Key == key) return nextItem;
                        scanItems.Enqueue(nextItem);
                    }
                }
                return null;
            }
            /// <summary>
            /// Obsahuje referenci na prvek, který je Root celého stromu tříd.
            /// Pokud this je Root, pak vrací sebe, jinak vrací Root svého předchůdce (LinkDown.Root).
            /// </summary>
            public ClassTreeItem Root
            {
                get
                {
                    return (this.IsRoot ? this : this.LinkDown.Root);
                }
            }
            #endregion
            #region Základní property
            /// <summary>
            /// Klíč = text, jak byl načten z FullTreePath (například: "C1180", "R9260", "Master.reference_subjektu")
            /// </summary>
            public string Key { get; private set; }
            /// <summary>
            /// Druh informace v této položce
            /// </summary>
            public ClassTreePathItemType ItemType { get; private set; }
            /// <summary>
            /// Číslo položky (číslo třídy / číslo vztahu)
            /// </summary>
            public int ItemNumber { get; private set; }
            /// <summary>
            /// Název sloupce atributu, holý = bez prefixu (tabulka ani alias).
            /// Je vyplněno pouze u typu Attribute.
            /// </summary>
            public string DbAttributeColumnName { get; private set; }
            /// <summary>
            /// Prvek je pro třídu?
            /// </summary>
            public bool IsClass { get { return (this.ItemNumber != 0 && this.ItemType == ClassTreePathItemType.Class); } }
            /// <summary>
            /// Prvek je pro vztah?
            /// </summary>
            public bool IsRelation { get { return (this.ItemNumber != 0 && (this.ItemType == ClassTreePathItemType.RelationLeftToRight || this.ItemType == ClassTreePathItemType.RelationRightToLeft)); } }
            /// <summary>
            /// Prvek je pro atribut?
            /// </summary>
            public bool IsAttribute { get { return (!String.IsNullOrEmpty(this.DbAttributeColumnName) && (this.ItemType == ClassTreePathItemType.AttributeSubject || this.ItemType == ClassTreePathItemType.AttributeMaster || this.ItemType == ClassTreePathItemType.AttributeUdaMaster || this.ItemType == ClassTreePathItemType.AttributeEntries || this.ItemType == ClassTreePathItemType.AttributeUdaEntries)); } }
            #region Kontrolní metody _Check???()
            private void _CheckIsClass(string message)
            {
                if (this.IsClass) return;
                throw new ClassTreeException("Nelze " + message + ", prvek má být Class, ale je " + this.Text);
            }
            private void _CheckIsRelation(string message)
            {
                if (this.IsRelation) return;
                throw new ClassTreeException("Nelze " + message + ", prvek má být Relation, ale je " + this.Text);
            }
            private void _CheckIsAttribute(string message)
            {
                if (this.IsAttribute) return;
                throw new ClassTreeException("Nelze " + message + ", prvek má být Attribute, ale je " + this.Text);
            }
            private void _CheckPrevIsClass(string message)
            {
                ClassTreeItem linkDown = this.LinkDown;
                if (linkDown != null && linkDown.IsClass) return;
                throw new ClassTreeException("Nelze " + message + ", předchozí prvek má být Class, ale je " + (linkDown == null ? "null" : linkDown.Text));
            }
            private void _CheckPrevIsRelation(string message)
            {
                ClassTreeItem linkDown = this.LinkDown;
                if (linkDown != null && linkDown.IsRelation) return;
                throw new ClassTreeException("Nelze " + message + ", předchozí prvek má být Relation, ale je " + (linkDown == null ? "null" : linkDown.Text));
            }
            #endregion
            #endregion
            #region Property a metody pro roli IsClass
            /// <summary>
            /// Data o třídě, pokud tento prvek je typu Class (IsClass)
            /// </summary>
            public ClassInfo ClassInfo
            {
                get
                {
                    if (this.IsClass)
                        return Repository.Classes[this.ItemNumber];
                    return null;
                }
            }
            /// <summary>
            /// Název identity sloupce v master tabulce aktuální třídy
            /// </summary>
            protected string DbColumnKeyMaster
            {
                get
                {
                    this._CheckIsClass("určit název master sloupce ve třídě");
                    if (this._DbColumnKeyMaster == null)
                    {
                        ClassInfo classInfo = this.ClassInfo;
                        string identity = classInfo.MasterIdentityColumn;
                        if (String.IsNullOrEmpty(identity))
                            throw new ClassTreeException("Nelze určit IDENTITY sloupec pro tabulku Master ve třídě " + classInfo.ToString());
                        this._DbColumnKeyMaster = identity;
                    }
                    return this._DbColumnKeyMaster;
                }
            }
            private string _DbColumnKeyMaster;
            /// <summary>
            /// Název identity sloupce v entries tabulce aktuální třídy
            /// </summary>
            protected string DbColumnKeyEntries
            {
                get
                {
                    this._CheckIsClass("určit název master sloupce ve třídě");
                    if (this._DbColumnKeyEntries == null)
                    {
                        ClassInfo classInfo = this.ClassInfo;
                        if (!classInfo.IsEntries)
                            throw new ClassTreeException("Nelze určit IDENTITY sloupec pro tabulku Entries ve třídě " + classInfo.ToString() + ", třída není položková.");
                        string identity = classInfo.EntriesIdentityColumn;
                        if (String.IsNullOrEmpty(identity))
                            throw new ClassTreeException("Nelze určit IDENTITY sloupec pro tabulku Entries ve třídě " + classInfo.ToString());
                        this._DbColumnKeyEntries = identity;
                    }
                    return this._DbColumnKeyEntries;
                }
            }
            private string _DbColumnKeyEntries;

            // zrušit odsud:
            #region Ke zrušení
            /// <summary>
            /// Najde ve frázi FROM tabulky, které odpovídají této třídě, a založí je do svého přehledu Tables, aby se o ně mohl opírat strom tříd
            /// </summary>
            /// <param name="phraseFrom"></param>
            internal void FindTablesInPhraseFrom(SqlParsePhraseFrom phraseFrom)
            {
                // Skupina tabulek počínaje FROM a další, které jsou propojené primárním klíčem
                Dictionary<string, SqlParseFromTable> fromDict = new Dictionary<string, SqlParseFromTable>();
                string identityMaster = this.ClassInfo.GetIdentityColumn(ClassTableType.Master).Trim().ToLower();
                string identityEntries = (this.ClassInfo.HasEntries ? this.ClassInfo.GetIdentityColumn(ClassTableType.Entries).Trim().ToLower() : null);
                for (int t = 0; t < phraseFrom.FromTableList.Count; t++)
                {
                    SqlParseFromTable table = phraseFrom.FromTableList[t];
                    bool isPrimary = (fromDict.Count == 0 || IsPrimaryJoinedToFrom(table, fromDict, identityMaster, identityEntries));
                    if (isPrimary)
                    {
                        fromDict.Add(table.AliasSource, table);
                        ClassTableType tableType = this.ClassInfo.GetTableType(table.Source);
                        if (tableType != ClassTableType.None)
                            // V tomto řádku FROM / JOIN je tabulka, která (podle jména) patří do this třídy.
                            // A protože je napojena jako FROM anebo JOIN přes primární klíč, pak ji akceptujeme jako opěrnou tabulku pro ClassTree:
                            this._AddTable(tableType, table.Source, table.Alias);
                    }
                }
            }
            /// <summary>
            /// Zjistí, zda daná tabulka ve svém JOIN ON výrazu má vazbu přes shodný klíč (vlevo = vpravo), který je předán 
            /// jako identityMaster nebo identityEntries (typicky: JOIN .. ON t1.cislo_subjektu = t2.cislo_subjektu).
            /// Pokud ano, pak ještě zjistí, zda alias tabulky, k níž se daná tabulka joinuje, je uveden v předané dictionary (fromDict).
            /// V této dictionary jsou uvedeny předchozí tabulky, které jsou vzájemně spojené primárním klíčem.
            /// </summary>
            /// <param name="table"></param>
            /// <param name="fromDict"></param>
            /// <param name="identityMaster"></param>
            /// <param name="identityEntries"></param>
            /// <returns></returns>
            internal bool IsPrimaryJoinedToFrom(SqlParseFromTable table, Dictionary<string, SqlParseFromTable> fromDict, string identityMaster, string identityEntries)
            {
                if (table.JoinType == JoinTableType.From) return false;
                List<OnExpressionEquals> equalsList = GetExpressionEqualsList(table);
                foreach (OnExpressionEquals eq in equalsList)
                {
                    if (eq.LeftColumn == eq.RightColumn &&
                        ((identityMaster != null && eq.LeftColumn == identityMaster) || (identityEntries != null && eq.LeftColumn == identityEntries)))
                    {   // Ve výrazu "=" v joinu je sloupec vlevo shodný se sloupcem vpravo, a je shodný s předaným identity columnem:
                        // Pokud dictionary fromDict obsahuje takový alias, k němuž je tento JOIN napojen, pak vrátím true:
                        if ((eq.TableOnLeft && fromDict.ContainsKey(eq.RightAlias)) ||
                            (eq.TableOnRight && fromDict.ContainsKey(eq.LeftAlias)))
                            return true;
                    }
                }
                return false;
            }
            /// <summary>
            /// Ze soupisu položek (výraz JOIN ON) vyseparuje výrazy na levé a pravé straně rovnítka,
            /// přičemž se pokusí je rozdělit na tabulku a sloupec (před a za poslední tečkou), a to na obou stranách rovnítka.
            /// </summary>
            /// <param name="itemList"></param>
            /// <returns></returns>
            private List<OnExpressionEquals> GetExpressionEqualsList(SqlParseFromTable table)
            {
                List<SqlParseItem> itemList = table.OnExpressions;
                string tableAlias = table.AliasSource;
                List<OnExpressionEquals> result = new List<OnExpressionEquals>();
                for (int i = 1; i < (itemList.Count - 1); i++)
                {
                    if (itemList[i].Text == "=")
                    {
                        OnExpressionEquals equals = OnExpressionEquals.Create(itemList[i - 1].Text, itemList[i + 1].Text, tableAlias);
                        if (equals != null)
                            result.Add(equals);
                    }
                }
                return result;
            }
            /// <summary>
            /// třída, která obsahuje výraz na levé a pravé straně rovnítka, výraz rozdělený na tabulku a na alias.
            /// </summary>
            private class OnExpressionEquals
            {
                public static OnExpressionEquals Create(string left, string right, string tableAlias)
                {
                    string leftAlias, leftColumn, rightAlias, rightColumn;
                    if (DbTableInfo.SplitFullDbColumn(left, out leftAlias, out leftColumn) &&
                        DbTableInfo.SplitFullDbColumn(right, out rightAlias, out rightColumn))
                    {
                        bool tableOnLeft = (leftAlias == tableAlias);
                        bool tableOnRight = (rightAlias == tableAlias);
                        return new OnExpressionEquals(leftAlias, leftColumn, tableOnLeft, rightAlias, rightColumn, tableOnRight);
                    }
                    return null;
                }
                private OnExpressionEquals(string leftAlias, string leftColumn, bool tableOnLeft, string rightAlias, string rightColumn, bool tableOnRight)
                {
                    this.LeftAlias = leftAlias;
                    this.LeftColumn = leftColumn;
                    this.TableOnLeft = tableOnLeft;
                    this.RightAlias = rightAlias;
                    this.RightColumn = rightColumn;
                    this.TableOnRight = tableOnRight;
                }
                internal string LeftAlias { get; private set; }
                internal string LeftColumn { get; private set; }
                internal bool TableOnLeft { get; private set; }
                internal string RightAlias { get; private set; }
                internal string RightColumn { get; private set; }
                internal bool TableOnRight { get; private set; }
            }

            #endregion


            #region Přidávání tabulek na logické úrovni (přidání tabulky jako FROM, přidání tabulky JOIN s autodetekcí zdroje a výrazu JOIN)
            /// <summary>
            /// Zařadí do svých použitých tabulek tabulku daného typu, jako FROM.
            /// </summary>
            /// <param name="tableType"></param>
            internal void AddTableAsFrom(ClassTableType tableType)
            {
                string message = "přidat tabulku " + tableType.ToString() + " jako FROM";
                this._CheckIsClass(message);
                string tableName = this.ClassInfo.GetTableName(tableType);
                if (tableName == null)
                    throw new ClassTreeException("Nelze " + message + ", třída " + this.ClassInfo.ToString() + " tuto tabulku nemá.");

                this._AddTable(tableType, tableName, ClassTreeTableJoinType.From, null);
            }
            /// <summary>
            /// <para>
            /// Metoda má za úkol naplnit do předaného objektu (který popisuje napojovací parametry) takové názvy vhodných sloupců (včetně aliasu), 
            /// které obsahují Identity klíč pro zdejší Master nebo Master + Entries.
            /// Sloupce se plní do ClassTreeRelConnect.JoinMasterToColumn a JoinEntryToColumn.
            /// <para>
            /// </para>
            /// Sloupce jsou k nalezení buď v některé vhodné zdejší tabulce, která už je napojená, nebo metoda zařídí buď napojení, 
            /// anebo dokáže převzít klíče "zespodu" napřímo = bez zapojování svých tabulek.
            /// Jak je to možné? Pro určité konstrukce stromu tříd není nutné, aby byla joinována kterákoli tabulka této třídy
            /// (pokud vedou dva vztahy zprava na tuto třídu, nebo pokud vztah Down je dynamický, pak máme klíč zdejšího záznamu určen zespodu).
            /// </para>
            /// </summary>
            /// <returns></returns>
            internal void FillCurrentKeyAlias(ClassTreeRelConnect upConnect)
            {
                this._CheckIsClass("určit klíč Master třídy");
                switch (upConnect.ConnectType)
                {
                    case ClassTreeRelConnectType.Master:
                        this._FillCurrentKeyAliasMaster(upConnect);
                        break;
                    case ClassTreeRelConnectType.MasterEntry:
                        this._FillCurrentKeyAliasMasterEntry(upConnect);
                        break;
                    default:
                        throw new ClassTreeException("Třída nemůže určit jména sloupců pro své číslo záznamu, požadovaný typ (ClassTreeRelConnect.ConnectType) není ani Master, ani MasterEntry. Došlo k chybnému vyvolání metody FillCurrentKeyAlias().");
                }
            }
            /// <summary>
            /// Určí do parametru upConnect, do property JoinMasterToColumn sloupec v některé (this nebo Down) zapojené tabulce, který obsahuje zdejší klíč this.Master.
            /// Parametr upConnect se chystá popisovat vztah vyšší třídy na naši třídu.
            /// </summary>
            /// <param name="upConnect"></param>
            private void _FillCurrentKeyAliasMaster(ClassTreeRelConnect upConnect)
            {
                ClassTreeTable table = this.FindTable(ClassTableType.Master, ClassTableType.Subjekt);
                if (table == null && this.HasTables)
                {   // Zatím nemáme zapojenou odpovídající tabulku Master.
                    // Pokud máme zapojenou jinou tabulku, tak Master zapojíme k ní, vnitřním linkem:
                    table = this._AddJoinTable(ClassTableType.Master);
                }

                if (table != null)
                {   // Pokud jsme našli naši tabulku Master nebo Subjekt (jiné tabulky ne, nemusí mít žádnou větu vztahující se k master !!!), tak do upConnect vepíšu Master klíčový sloupec s jejím aliasem:
                    upConnect.JoinMasterToColumn = table.GetAliasedColumn(this.DbColumnKeyMaster);
                }
                else
                {   // Zatím nemáme zapojenou odpovídající tabulku. Buď ji zapojím, anebo toho nebude zapotřebí: pokud vztah zespodu vede na náš Master, tak mi stačí převzít klíč zespodu:
                    this._CheckPrevIsRelation("určit klíč Master třídy pro vztah nahoru");
                    ClassTreeRelConnect relDownConnect = this.LinkDown.GetRelationUp(this);
                    // Pokud vztah "zespodu" vede na náš Master (nebo Master + Entry) klíč, tak sebe nemusím zapojovat a převezmu alias Master sloupce zespodu a předám ho nahoru:
                    switch (relDownConnect.ConnectType)
                    {
                        case ClassTreeRelConnectType.Column:
                            // Dolní vztah ukazuje na naši třídu někam do vztahového sloupce některé tabulky, budeme muset tuto tabulku JOINovat a postavit standardní konstrukci v této třídě:
                            table = this.AddJoinTable(relDownConnect.ConnectTableType, false, ref relDownConnect);
                            upConnect.JoinMasterToColumn = table.GetAliasedColumn(this.DbColumnKeyMaster);
                            break;
                        case ClassTreeRelConnectType.Master:
                        case ClassTreeRelConnectType.MasterEntry:
                            // Dolní vztah se váže na náš Identity column = ve své property JoinMasterToColumn nese sloupec, který obsahuje naše číslo záznamu:
                            // A protože ve směru Up je po nás požadováno pouze číslo Master, tak ignoruji vztahový sloupec Entries.
                            upConnect.JoinMasterToColumn = relDownConnect.JoinMasterToColumn;
                            // Pokud bychom to takhle nezkrátili, pak bychom provedli následující JOIN:
                            //   LEFT OUTER JOIN this.tabulka ON this.tabulka.cislo_subjektu = downConnect.JoinMasterToColumn
                            //  a nahoru bychom předávali náš sloupec this.tabulka.cislo_subjektu jako joinovací sloupec pro vyšší vztah.
                            //  přičemž this.tabulka.cislo_subjektu musí obsahovat stejný klíč, jako sloupec downConnect.JoinMasterToColumn
                            //   (anebo this.tabulka.cislo_subjektu může být NULL).
                            // JOINEM bychom na rozdíl od přímého provázání vyřešili stav, kdy this záznam neexistuje (NULL), 
                            //   ale oba okolní záznamy (Down i Up) se na něj odkazují = což je porušení konzistence vztahů v databázi (a to nepředpokládáme...)
                            break;
                    }
                }
            }
            /// <summary>
            /// Určí do relConnect do property JoinMasterToColumn sloupec v některé zapojené tabulce, který obsahuje klíč this.Master, a do JoinEntryToColumn klíč this.Entries.
            /// </summary>
            /// <param name="relConnect"></param>
            private void _FillCurrentKeyAliasMasterEntry(ClassTreeRelConnect upConnect)
            {
                ClassTreeTable table = this.FindTable(ClassTableType.Entries, ClassTableType.UdaEntries);
                if (table == null && this.HasTables)
                {   // Zatím nemáme zapojenou odpovídající tabulku Entries.
                    // Pokud máme zapojenou jinou tabulku, tak Entries zapojíme k ní, vnitřním linkem:
                    table = this._AddJoinTable(ClassTableType.Entries);
                }

                if (table != null)
                {   // Pokud jsme našli naši tabulku Entries nebo UdaEntries (nebo jsme ji interně zapojili) (jiné tabulky ne, nemusí mít žádnou větu vztahující se k položkám !!!), 
                    //   tak do upConnect vepíšu klíčový sloupec Master a Entries i s jeho aliasem:
                    upConnect.JoinMasterToColumn = table.GetAliasedColumn(this.DbColumnKeyMaster);
                    upConnect.JoinEntryToColumn = table.GetAliasedColumn(this.DbColumnKeyEntries);
                }
                else
                {   // Pokud nemáme žádnou tabulku zapojenou, tak zapojíme tabulku Entries na klíč pocházející z Down prvku.
                    // Pokud ale vztah zespodu vede na náš Master, tak mi stačí převzít klíč zespodu a zdejší tabulku nemusím zapojovat vůbec.
                    this._CheckPrevIsRelation("určit klíč Entries třídy pro vztah nahoru");
                    ClassTreeRelConnect relDownConnect = this.LinkDown.GetRelationUp(this);
                    // Pokud vztah "zespodu" vede na náš Master (nebo Master + Entry) klíč, tak sebe nemusím zapojovat a převezmu alias Master sloupce zespodu a předám ho nahoru:
                    switch (relDownConnect.ConnectType)
                    {
                        case ClassTreeRelConnectType.Column:
                            // Dolní vztah ukazuje na naši třídu někam do vztahového sloupce některé tabulky, budeme muset tuto tabulku JOINovat a postavit standardní konstrukci v této třídě:
                            table = this.AddJoinTable(relDownConnect.ConnectTableType, false, ref relDownConnect);
                            if (table.TableType != ClassTableType.Entries && table.TableType != ClassTableType.UdaEntries)
                            {   // Statický vztah mi zapojil jinou než položkovou tabulku, a protože potřebuji položky kvůli číslu objektu, tak si je budu muset joinovat:
                                table = this._AddJoinTable(ClassTableType.Entries);
                            }
                            upConnect.JoinMasterToColumn = table.GetAliasedColumn(this.DbColumnKeyMaster);
                            upConnect.JoinEntryToColumn = table.GetAliasedColumn(this.DbColumnKeyEntries);
                            break;
                        case ClassTreeRelConnectType.Master:
                            // Dolní vztah se váže na náš Identity column Master, ale my potřebujeme i Entries: tak zapojíme Entries:
                            table = this._AddJoinTable(ClassTableType.Entries, false, ref relDownConnect);
                            upConnect.JoinMasterToColumn = table.GetAliasedColumn(this.DbColumnKeyMaster);
                            upConnect.JoinEntryToColumn = table.GetAliasedColumn(this.DbColumnKeyEntries);
                            break;
                        case ClassTreeRelConnectType.MasterEntry:
                            // Dolní vztah se váže na náš Identity column = ve své property JoinMasterToColumn nese sloupec, který obsahuje naše číslo záznamu:
                            // A protože ve směru Up je po nás požadováno pouze číslo Master, tak ignoruji vztahový sloupec Entries.
                            upConnect.JoinMasterToColumn = relDownConnect.JoinMasterToColumn;
                            // Pokud bychom to takhle nezkrátili, pak bychom provedli následující JOIN:
                            //   LEFT OUTER JOIN this.tabulka ON this.tabulka.cislo_subjektu = downConnect.JoinMasterToColumn
                            //  a nahoru bychom předávali náš sloupec this.tabulka.cislo_subjektu jako joinovací sloupec pro vyšší vztah.
                            //  přičemž this.tabulka.cislo_subjektu musí obsahovat stejný klíč, jako sloupec downConnect.JoinMasterToColumn
                            //   (anebo this.tabulka.cislo_subjektu může být NULL).
                            // JOINEM bychom na rozdíl od přímého provázání vyřešili stav, kdy this záznam neexistuje (NULL), 
                            //   ale oba okolní záznamy (Down i Up) se na něj odkazují = což je porušení konzistence vztahů v databázi (a to nepředpokládáme...)
                            break;
                    }
                }
            }
            #endregion
            #region Přidávání dalších tabulek vnitřním joinem (uvnitř třídy!) k již připojeným tabulkám v rámci tohoto prvku
            /// <summary>
            /// Zařadí do svých tabulek danou tabulku daného typu, jako JOIN.
            /// Metoda sama určuje, k čemu se bude joinovat a jak.
            /// Tato metoda je povolena pouze pro prvek typu Class.
            /// Zapojuje požadovanou tabulku joinem buď v rámci this (když this už nějakou tabulku má zapojenou), anebo do Down větve (když je to potřeba).
            /// </summary>
            /// <param name="tableType"></param>
            /// <returns></returns>
            internal ClassTreeTable AddJoinTable(ClassTableType tableType)
            {
                ClassTreeRelConnect relDownConnect = null;
                return this.AddJoinTable(tableType, true, ref relDownConnect);
            }
            /// <summary>
            /// Zařadí do svých tabulek danou tabulku daného typu, jako JOIN.
            /// Metoda sama určuje, k čemu se bude joinovat a jak.
            /// Tato metoda je povolena pouze pro prvek typu Class.
            /// Zapojuje požadovanou tabulku joinem buď v rámci this (když this už nějakou tabulku má zapojenou), anebo do Down větve (když je to potřeba).
            /// </summary>
            /// <param name="tableType"></param>
            /// <returns></returns>
            internal ClassTreeTable AddJoinTable(ClassTableType tableType, bool enableInnerJoin, ref ClassTreeRelConnect relDownConnect)
            {
                this._CheckIsClass("přidat tabulku třídy (" + tableType.ToString() + ")");
                ClassTreeTable table = this.FindTable(tableType);
                if (table == null)
                {
                    if (!enableInnerJoin)
                        this._CheckPrevIsRelation("zapojit tabulku " + tableType.ToString() + " do třídy " + this.ClassInfo.ToString());
                    table = this._AddJoinTable(tableType, enableInnerJoin, ref relDownConnect);
                }
                return table;
            }
            /// <summary>
            /// Přidá další tabulku do tabulek použitých v tomto prvku (třídě).
            /// Provede se kontrola, zda tabulka daného typu je v aktuální třídě relevantní.
            /// V této metodě neprobíhá kontrola _CheckIsClass() ani se neprovádí opakované hledání požadované tabulky v seznamu tabulek již použitých.
            /// </summary>
            /// <param name="tableType"></param>
            /// <returns></returns>
            private ClassTreeTable _AddJoinTable(ClassTableType tableType)
            {
                ClassTreeRelConnect relDownConnect = null;
                return this._AddJoinTable(tableType, true, ref relDownConnect);
            }
            /// <summary>
            /// Přidá další tabulku do tabulek použitých v tomto prvku (třídě).
            /// Provede se kontrola, zda tabulka daného typu je v aktuální třídě relevantní.
            /// V této metodě neprobíhá kontrola _CheckIsClass() ani se neprovádí opakované hledání požadované tabulky v seznamu tabulek již použitých.
            /// </summary>
            /// <param name="tableType"></param>
            /// <returns></returns>
            private ClassTreeTable _AddJoinTable(ClassTableType tableType, bool enableInnerJoin, ref ClassTreeRelConnect relDownConnect)
            {
                string tableName = this.ClassInfo.GetTableName(tableType);
                if (String.IsNullOrEmpty(tableName))
                    throw new ClassTreeException("Nelze přidat tabulku " + tableType.ToString() + ", tabulka ve třídě " + this.ClassInfo.ToString() + " neexistuje.");

                switch (tableType)
                {
                    case ClassTableType.Subjekt:
                        return this._AddJoinTableSubject(tableName, enableInnerJoin, ref relDownConnect);
                    case ClassTableType.Master:
                        return this._AddJoinTableMaster(tableName, enableInnerJoin, ref relDownConnect);
                    case ClassTableType.Entries:
                        return this._AddJoinTableEntries(tableName, enableInnerJoin, ref relDownConnect);
                    case ClassTableType.UdaMaster:
                        return this._AddJoinTableUdaMaster(tableName, enableInnerJoin, ref relDownConnect);
                    case ClassTableType.UdaEntries:
                        return this._AddJoinTableUdaEntries(tableName, enableInnerJoin, ref relDownConnect);
                }
                throw new ClassTreeException("Nelze přidat tabulku " + tableType.ToString() + ", typ není ve třídě " + this.ClassInfo.ToString() + " známý.");
            }
            /// <summary>
            /// Napojí danou datovou tabulku - Subjekty (v typu IsClass).
            /// Buď ji napojí do vnitřních struktur, anebo ji připojí k Down části stromu.
            /// Vrátí ji.
            /// </summary>
            /// <param name="tableName"></param>
            private ClassTreeTable _AddJoinTableSubject(string tableName, bool enableInnerJoin, ref ClassTreeRelConnect relDownConnect)
            {
                ClassTreeTable table;
                if (!this._TryFindOtherTableToJoin(ClassTableType.Subjekt, new ClassTableType[] { ClassTableType.Master, ClassTableType.Entries, ClassTableType.UdaMaster, ClassTableType.UdaEntries }, enableInnerJoin, ref relDownConnect, out table))
                    return table;

                // This prvek je už zapojen do stromu, tabulka table je první zapojená, nyní zapojím subjekty k této tabulce:
                return this._AddTable(ClassTableType.Subjekt, tableName, ClassTreeTableJoinType.LeftOuterJoin, ClassTreeTable.JOIN_CURRENTALIAS_TOKEN + "." + this.DbColumnKeyMaster + " = " + table.CurrentAlias + "." + this.DbColumnKeyMaster);
            }
            /// <summary>
            /// Napojí danou datovou tabulku - Master (v typu IsClass).
            /// Buď ji napojí do vnitřních struktur, anebo ji připojí k Down části stromu.
            /// Vrátí ji.
            /// </summary>
            /// <param name="tableName"></param>
            private ClassTreeTable _AddJoinTableMaster(string tableName, bool enableInnerJoin, ref ClassTreeRelConnect relDownConnect)
            {
                ClassTreeTable table;
                if (!this._TryFindOtherTableToJoin(ClassTableType.Master, new ClassTableType[] { ClassTableType.Subjekt, ClassTableType.Entries, ClassTableType.UdaMaster, ClassTableType.UdaEntries }, enableInnerJoin, ref relDownConnect, out table))
                    return table;

                // This prvek je už zapojen do stromu, tabulka table je první zapojená, nyní zapojím Master k této tabulce:
                return this._AddTable(ClassTableType.Master, tableName, ClassTreeTableJoinType.LeftOuterJoin, ClassTreeTable.JOIN_CURRENTALIAS_TOKEN + "." + this.DbColumnKeyMaster + " = " + table.CurrentAlias + "." + this.DbColumnKeyMaster);
            }
            /// <summary>
            /// Napojí danou datovou tabulku - Entries (v typu IsClass).
            /// Buď ji napojí do vnitřních struktur, anebo ji připojí k Down části stromu.
            /// Vrátí ji.
            /// </summary>
            /// <param name="tableName"></param>
            private ClassTreeTable _AddJoinTableEntries(string tableName, bool enableInnerJoin, ref ClassTreeRelConnect relDownConnect)
            {
                ClassTreeTable table;
                if (!this._TryFindOtherTableToJoin(ClassTableType.Entries, new ClassTableType[] { ClassTableType.Subjekt, ClassTableType.Master, ClassTableType.UdaMaster, ClassTableType.UdaEntries }, enableInnerJoin, ref relDownConnect, out table))
                    return table;

                // This prvek je už zapojen do stromu, tabulka table je první zapojená, nyní zapojím Entries k této tabulce:
                string joinExpression = "(" + ClassTreeTable.JOIN_CURRENTALIAS_TOKEN + "." + this.DbColumnKeyMaster + " = " + table.CurrentAlias + "." + this.DbColumnKeyMaster;
                if (table.TableType == ClassTableType.UdaEntries)
                    joinExpression += " and " + ClassTreeTable.JOIN_CURRENTALIAS_TOKEN + "." + this.DbColumnKeyEntries + " = " + table.CurrentAlias + "." + this.DbColumnKeyEntries;
                joinExpression += ")";

                return this._AddTable(ClassTableType.Entries, tableName, ClassTreeTableJoinType.LeftOuterJoin, joinExpression);
            }
            /// <summary>
            /// Napojí danou datovou tabulku - UDA Master (v typu IsClass).
            /// Buď ji napojí do vnitřních struktur, anebo ji připojí k Down části stromu.
            /// Vrátí ji.
            /// </summary>
            /// <param name="tableName"></param>
            private ClassTreeTable _AddJoinTableUdaMaster(string tableName, bool enableInnerJoin, ref ClassTreeRelConnect relDownConnect)
            {
                ClassTreeTable table;
                if (!this._TryFindOtherTableToJoin(ClassTableType.UdaMaster, new ClassTableType[] { ClassTableType.Subjekt, ClassTableType.Master, ClassTableType.Entries, ClassTableType.UdaEntries }, enableInnerJoin, ref relDownConnect, out table))
                    return table;

                // This prvek je už zapojen do stromu, tabulka table je první zapojená, nyní zapojím UdaMaster k této tabulce:
                return this._AddTable(ClassTableType.UdaMaster, tableName, ClassTreeTableJoinType.LeftOuterJoin, ClassTreeTable.JOIN_CURRENTALIAS_TOKEN + "." + this.DbColumnKeyMaster + " = " + table.CurrentAlias + "." + this.DbColumnKeyMaster);
            }
            /// <summary>
            /// Napojí danou datovou tabulku - UDA Entries (v typu IsClass).
            /// Buď ji napojí do vnitřních struktur, anebo ji připojí k Down části stromu.
            /// Vrátí ji.
            /// </summary>
            /// <param name="tableName"></param>
            private ClassTreeTable _AddJoinTableUdaEntries(string tableName, bool enableInnerJoin, ref ClassTreeRelConnect relDownConnect)
            {
                ClassTreeTable table;
                if (!this._TryFindOtherTableToJoin(ClassTableType.UdaEntries, new ClassTableType[] { ClassTableType.Entries, ClassTableType.Subjekt, ClassTableType.Master, ClassTableType.UdaMaster }, enableInnerJoin, ref relDownConnect, out table))
                    return table;

                // This prvek je už zapojen do stromu, tabulka table je první zapojená, nyní zapojím UdaEntries k této tabulce:
                string joinExpression = "(" + ClassTreeTable.JOIN_CURRENTALIAS_TOKEN + "." + this.DbColumnKeyMaster + " = " + table.CurrentAlias + "." + this.DbColumnKeyMaster;
                if (table.TableType == ClassTableType.Entries)
                    joinExpression += " and " + ClassTreeTable.JOIN_CURRENTALIAS_TOKEN + "." + this.DbColumnKeyEntries + " = " + table.CurrentAlias + "." + this.DbColumnKeyEntries;
                joinExpression += ")";

                return this._AddTable(ClassTableType.UdaEntries, tableName, ClassTreeTableJoinType.LeftOuterJoin, joinExpression);
            }
            /// <summary>
            /// Zkusí najít danou tabulku. Pokud ji najde, uloží ji do out parametru table, a vrátí false !!!, protože není třeba zakládat novou tabulku ani ji joinovat, danou tabulku už máme.
            /// Pokud nenajde tabulku přímo hledanou, zkusí najít jinou použitou tabulku podle povolených typů tabulky (vyjmenované v joinToTypes).
            /// Pokud tu jinou tabulku najde, uloží ji do out table a vrátí true. Značí to, že výchozí tabulku sice nemáme, ale máme jinou použitou (z povolených typů), a potřebnou tabulku k ní můžeme joinovat a přidat.
            /// Pokud nenajde ani přímo hledanou, ani žádnou z povolených tabulek, pak se pokusí napojit sebe jako Item do stromu, napojí se do vztahu dolů (LinkDown).
            /// Pokud napojení bude provedeno do explictní tabulky (nikoli "na klíč"), pak vrací: true pokud ta explicitní tabulka je shodou okolností ta požadovaná (parametr tableType), 
            /// protože pak volající zase nemusí zajišťovat JOIN své tabulky. 
            /// Pokud se napojí explicitní tabulka, a bude jiná než přímo hledaná, pak se prohledají další povolené tabulky. 
            /// Pokud se explicitně napojila tabulka z povolených, předá se do out table a vrací se true, volající může svoji napojit na nově přidanou tabulku, protože je povolená.
            /// Pokud se explicitně napojila tabulka nepovolená, pak metoda vezme první z povolených (není napojená), a předá žádost o její napojení (jako mezikrok), a pak ji dá do out table a vrátí true.
            /// Pokud je vztah takový, že nežádá napojení explicitní tabulky (je to vztah "na klíč"), pak vztah přináší typicky číslo subjektu (a případně objektu, u dynamických položkových vztahů zprava).
            /// Pak, pokud se žádá napojení konkrétní tabulky, pak se provede její přidání do tabulek přímo zde, protože bude mít jiný (explicitní) JOIN.
            /// Tabulka se vloží do out table a vrátí se false, protože volající už nemusí tabulku napojovat svými silami.
            /// Pokud se nežádá napojení konkrétní tabulky (tableType = None), pak se nic nenapojuje, table zůstává null a vrací se true = volající má pokračovat. 
            /// Takovým volajícím (bez uvedení typu tabulky) je metoda this._FillCurrentKeyAliasMaster() nebo this._FillCurrentKeyAliasEntries().
            /// </summary>
            /// <param name="tableType"></param>
            /// <param name="joinToTypes"></param>
            /// <param name="table"></param>
            /// <returns></returns>
            private bool _TryFindOtherTableToJoin(ClassTableType tableType, ClassTableType[] joinToTypes, bool enableInnerJoin, ref ClassTreeRelConnect relDownConnect, out ClassTreeTable table)
            {
                // Pokud najdeme přímo potřebnou tabulku, tak to není k JOINování:
                table = this.FindTable(tableType);
                if (table != null)
                    return false;           // false = není to k JOINování

                // Pokusíme se najít některou z povolených tabulek, pak bychom mohli potřebnou tabulku k ní JOINovat:
                if (joinToTypes != null && enableInnerJoin)
                {   // (...tedy, pokud jsou definované povolené tabulky pro JOIN)
                    table = this.FindTable(joinToTypes);
                    if (table != null)
                        return true;        // true  = potřebnou tabulku nemáme, ale můžeme ji JOINovat k tabulce nalezené
                }

                // Něco chceme (tableType) a nemáme to. Mohli bychom to joinovat uvnitř třídy (joinToTypes) ale ani to nemáme.
                // Budeme se muset napojit dolů:
                if (relDownConnect == null)
                {   // Pokud jsme nedostali explicitní instrukce k napojení dolů, získáme je svépomocí:
                    this._CheckPrevIsRelation("zapojit prvek " + this.Text + " do stromu");
                    relDownConnect = this.LinkDown.GetRelationUp(this);
                }

                ClassTableType relTableType = ClassTableType.None;
                string joinExpression = null;
                switch (relDownConnect.ConnectType)
                {
                    case ClassTreeRelConnectType.Column:
                        // Pokud je vztah na konkrétní tabulku (Column), pak tu tabulku musíme přijoinovat (tabulka je určena v ConnectTableType):
                        relTableType = relDownConnect.ConnectTableType;
                        joinExpression = ClassTreeTable.JOIN_CURRENTALIAS_TOKEN + "." + relDownConnect.ConnectDbColumn + " = " + relDownConnect.JoinMasterToColumn;
                        break;

                    case ClassTreeRelConnectType.Master:
                        // Jde o vztah "na klíč": zdola na nás někdo ukazuje, na naše číslo subjektu/nonsubjektu (Master).
                        if (tableType == ClassTableType.None)
                        {   // Nebyl požadován konkrétní typ tabulky = volá nás metoda pro získání klíče kvůli vztahu nahoru. Nebudu nic joinovat, vztah zdola je "na klíč", to by mělo volající metodě stačit:
                            table = null;
                            return false;
                        }
                        // Je požadován konkrétní typ tabulky, my bychom rádi joinovali Master, ale pokud je požadován Subjekt tak vyhovíme:
                        relTableType = (tableType == ClassTableType.Subjekt ? ClassTableType.Subjekt : ClassTableType.Master);
                        joinExpression = ClassTreeTable.JOIN_CURRENTALIAS_TOKEN + "." + this.DbColumnKeyMaster + " = " + relDownConnect.JoinMasterToColumn;
                        break;

                    case ClassTreeRelConnectType.MasterEntry:
                        // Jde o vztah "na klíč": zdola na nás někdo ukazuje, na naše číslo subjektu/nonsubjektu (Master) a na naše číslo položky (Entries).
                        if (tableType == ClassTableType.None)
                        {   // Nebyl požadován konkrétní typ tabulky = volá nás metoda pro získání klíče kvůli vztahu nahoru. Nebudu nic joinovat, vztah zdola je "na klíč", to by mělo volající metodě stačit:
                            table = null;
                            return false;
                        }
                        // Je požadován konkrétní typ tabulky, ale my budeme joinovat Entries, protože jenom tam můžeme uplatnit číslo subjektu a objektu:
                        relTableType = ClassTableType.Entries;
                        joinExpression = "(" + ClassTreeTable.JOIN_CURRENTALIAS_TOKEN + "." + this.DbColumnKeyMaster + " = " + relDownConnect.JoinMasterToColumn + " and " +
                                               ClassTreeTable.JOIN_CURRENTALIAS_TOKEN + "." + this.DbColumnKeyEntries + " = " + relDownConnect.JoinEntryToColumn + ")";
                        break;
                }

                // Zde napojím danou tabulku (například Master v this prvku) na nějaký sloupec ze spodní části stromu (specifikace je už v proměnných):
                string relTableName = this.ClassInfo.GetTableName(relTableType);
                if (relTableName == null)
                    throw new ClassTreeException("Nelze připojit tabulku " + relTableType.ToString() + ", třída " + this.ClassInfo.ToString() + " takovou tabulku nepoužívá.");
                table = this._AddTable(relTableType, relTableName, ClassTreeTableJoinType.LeftOuterJoin, joinExpression);

                if (relTableType == tableType)                      // Pokud jsem napojil tabulku, která byla shodou okolností požadovaná, mám hotovo - vrátím false, protože volající nic dalšího nemusí dělat.
                    return false;

                // Zjistím, zda nově založená tabulka je jednoho z daných typů:
                if (table.CorrespondingToType(joinToTypes) >= 0)
                    return true;                                    // Napojená tabulka není přímo ta požadovaná, ale vyhovuje jako základ pro JOIN té hledané - vrátím true, protože tabulku máme, a může proběhnout JOIN

                // Napojil jsem tabulku (dolů do stromu), ale není to ta požadovaná, a ani povolená pro JOINy.
                // Pokud máme definovaný typ požadované tabulky (tableType) i povolené typy pro JOIN (joinToTypes), tak napojíme tabulku prvního povoleného typu joinToTypes[0], a volajícímu ji dáme k jeho JOINu:
                if (tableType != ClassTableType.None && (joinToTypes != null && joinToTypes.Length > 0))
                {
                    table = this._AddJoinTable(joinToTypes[0]);     // Volající sám souhlasil s tím, že se může najoinovat na tabulku tohoto typu.
                    return true;
                }
                table = null;
                return false;
            }
            #endregion
            #endregion
            #region Property a metody pro roli IsRelation
            /// <summary>
            /// Data o vztahu, pokud tento prvek je typu Relation (IsRelation)
            /// </summary>
            public RelationInfo RelationInfo
            {
                get
                {
                    if (this.IsRelation)
                        return Repository.Relations[this.ItemNumber];
                    return null;
                }
            }
            /// <summary>
            /// Strana vztahu = kam vede: pro vztah zleva doprava obsahuje Right, pro vztah zprava doleva obsahuje Left, jinak None
            /// </summary>
            public RelationSideType RelationSide
            {
                get
                {
                    if (!this.IsRelation)
                        return RelationSideType.None;
                    return (this.ItemType == ClassTreePathItemType.RelationLeftToRight ? RelationSideType.Right :
                           (this.ItemType == ClassTreePathItemType.RelationRightToLeft ? RelationSideType.Left : RelationSideType.None));
                }
            }
            #region Metody použité v roli IsRelation
            /// <summary>
            /// Vrátí popis předávacích bodů vztahu ze směru Down do Up třídy (Up třída je předána v parametru).
            /// Používá se na prvku, který je Relation.
            /// Tuto metodu využívá vyšší prvek, který je IsClass, když se chce zapojit do stromu - chce joinovat některou svoji tabulku k nižší části stromu.
            /// Zdejší vztah podle svého charakteru určí, který vyšší prvek se bude joinovat k jakému nižšímu sloupci.
            /// </summary>
            /// <param name="upClass"></param>
            /// <returns></returns>
            private ClassTreeRelConnect GetRelationUp(ClassTreeItem upClass)
            {
                this._CheckIsRelation("zapojit prvek " + this.Text + " do stromu");
                this._CheckPrevIsClass("zapojit prvek " + this.Text + " do stromu");

                switch (this.CurrentRelationMode)
                {
                    case RelationMode.StaticMasterToRight:
                        return this._GetRelationUpStaticMasterToRight(upClass);
                    case RelationMode.StaticEntryToRight:
                        return this._GetRelationUpStaticEntryToRight(upClass);
                    case RelationMode.StaticMasterToLeft:
                        return this._GetRelationUpStaticMasterToLeft(upClass);
                    case RelationMode.StaticEntryToLeft:
                        return this._GetRelationUpStaticEntryToLeft(upClass);
                    case RelationMode.DynamicMasterToRight:
                        return this._GetRelationUpDynamicMasterToRight(upClass);
                    case RelationMode.DynamicEntryToRight:
                        return this._GetRelationUpDynamicEntryToRight(upClass);
                    case RelationMode.DynamicMasterToLeft:
                        return this._GetRelationUpDynamicMasterToLeft(upClass);
                    case RelationMode.DynamicEntryToLeft:
                        return this._GetRelationUpDynamicEntryToLeft(upClass);
                }
                throw new ClassTreeException("Nelze získat data pro Join pro vztah " + this.Text + ", nebyl určen režim vztahu (CurrentRelationMode).");
            }
            /// <summary>
            /// Vrátí napojovací údaje pro vztah StaticMasterToRight
            /// </summary>
            /// <param name="upClass"></param>
            /// <returns></returns>
            private ClassTreeRelConnect _GetRelationUpStaticMasterToRight(ClassTreeItem upClass)
            {
                // Vztah vede z Down třídy, z některé její tabulky (je to vztah hlavičkový), ze sloupce statického vztahu
                //  doprava do třídy Up, na její číslo Master (subjekt, nonsubjekt):
                string joinMasterToColumn = this.LinkDown.GetColumnAlias(this.RelationInfo.TableName, this.RelationInfo.DbColumn);
                return new ClassTreeRelConnect(ClassTreeRelConnectType.Master, ClassTableType.None, null, joinMasterToColumn, null);
            }
            /// <summary>
            /// Vrátí napojovací údaje pro vztah StaticEntryToRight
            /// </summary>
            /// <param name="upClass"></param>
            /// <returns></returns>
            private ClassTreeRelConnect _GetRelationUpStaticEntryToRight(ClassTreeItem upClass)
            {
                // Vztah vede z Down třídy, z některé její tabulky (je to vztah položkový), ze sloupce doprava na číslo Master (subjekt, nonsubjekt):
                string joinMasterToColumn = this.LinkDown.GetColumnAlias(this.RelationInfo.TableName, this.RelationInfo.DbColumn);
                return new ClassTreeRelConnect(ClassTreeRelConnectType.Master, ClassTableType.None, null, joinMasterToColumn, null);
            }
            /// <summary>
            /// Vrátí napojovací údaje pro vztah StaticMasterToLeft
            /// </summary>
            /// <param name="upClass"></param>
            /// <returns></returns>
            private ClassTreeRelConnect _GetRelationUpStaticMasterToLeft(ClassTreeItem upClass)
            {
                // Vztah fyzicky vede z Up třídy z některého sloupce (je to vztah hlavičkový) na klíč třídy Down.
                // Zde vztah řešíme z opačné strany: ve třídě Down vyhledáme její MasterKey,
                //  a do třídy Up předáme připojovací údaje, které ve třídě Up vyhledají sloupec statického vztahu 
                //  a napojí jej na MasterKey ze třídy Down:
                ClassTreeRelConnect downRelConnect = new ClassTreeRelConnect(ClassTreeRelConnectType.Master);
                this.LinkDown.FillCurrentKeyAlias(downRelConnect);
                return new ClassTreeRelConnect(ClassTreeRelConnectType.Column, this.RelationInfo.TableType, this.RelationInfo.DbColumn, downRelConnect.JoinMasterToColumn, null);
            }
            /// <summary>
            /// Vrátí napojovací údaje pro vztah 
            /// </summary>
            /// <param name="upClass"></param>
            /// <returns></returns>
            private ClassTreeRelConnect _GetRelationUpStaticEntryToLeft(ClassTreeItem upClass)
            {
                // Vztah fyzicky vede z Up třídy z některého sloupce (je to vztah položkový) na klíč třídy Down.
                // Zde vztah řešíme z opačné strany: ve třídě Down vyhledáme její MasterKey,
                //  a do třídy Up předáme připojovací údaje, které ve třídě Up vyhledají sloupec statického vztahu 
                //  a napojí jej na MasterKey ze třídy Down:
                ClassTreeRelConnect downRelConnect = new ClassTreeRelConnect(ClassTreeRelConnectType.Master);
                this.LinkDown.FillCurrentKeyAlias(downRelConnect);
                return new ClassTreeRelConnect(ClassTreeRelConnectType.Column, this.RelationInfo.TableType, this.RelationInfo.DbColumn, downRelConnect.JoinMasterToColumn, null);
            }
            /// <summary>
            /// Vrátí napojovací údaje pro vztah 
            /// </summary>
            /// <param name="upClass"></param>
            /// <returns></returns>
            private ClassTreeRelConnect _GetRelationUpDynamicMasterToRight(ClassTreeItem upClass)
            {
                // Dynamický vztah, v třídě Down má výchozí stranu (číslo záznamu), následuje tabulka dynamických vztahů vztahysubjektu,
                //  a do třídy Up se váže na její Master klíč, ze sloupce "navázaný záznam" z dynamického vztahu.
                // Poznámka: pokud už v tomto prvku máme tabulku typu None, pak je vztah už připraven.
                ClassTreeTable table = this.FindTable(ClassTableType.None);
                if (table == null)
                {
                    ClassTreeRelConnect downRelConnect = new ClassTreeRelConnect(ClassTreeRelConnectType.Master);
                    this.LinkDown.FillCurrentKeyAlias(downRelConnect);
                    string joinDownExpression = "(" + ClassTreeTable.JOIN_CURRENTALIAS_TOKEN + ".cislo_subjektu = " + downRelConnect.JoinMasterToColumn +
                        " and " + ClassTreeTable.JOIN_CURRENTALIAS_TOKEN + ".cislo_vztahu = " + this.RelationInfo.CisloVztahu.ToString() + ")";
                    table = this._AddTable(ClassTableType.None, "lcs.vztahysubjektu", ClassTreeTableJoinType.LeftOuterJoin, joinDownExpression);
                }
                string joinMasterToColumn = table.GetAliasedColumn("cislo_vztaz_subjektu");
                return new ClassTreeRelConnect(ClassTreeRelConnectType.Master, ClassTableType.None, null, joinMasterToColumn, null);
            }
            /// <summary>
            /// Vrátí napojovací údaje pro vztah 
            /// </summary>
            /// <param name="upClass"></param>
            /// <returns></returns>
            private ClassTreeRelConnect _GetRelationUpDynamicEntryToRight(ClassTreeItem upClass)
            {
                // Dynamický vztah, v třídě Down má výchozí stranu (číslo záznamu + položky), následuje tabulka dynamických vztahů objektsubjekt,
                //  a do třídy Up se váže na její Master klíč, ze sloupce "navázaný záznam" z dynamického vztahu.
                // Poznámka: pokud už v tomto prvku máme tabulku typu None, pak je vztah už připraven.
                ClassTreeTable table = this.FindTable(ClassTableType.None);
                if (table == null)
                {
                    ClassTreeRelConnect downRelConnect = new ClassTreeRelConnect(ClassTreeRelConnectType.MasterEntry);
                    this.LinkDown.FillCurrentKeyAlias(downRelConnect);
                    string joinDownExpression = "(" + ClassTreeTable.JOIN_CURRENTALIAS_TOKEN + ".cislo_dokladu = " + downRelConnect.JoinMasterToColumn +
                        " and " + ClassTreeTable.JOIN_CURRENTALIAS_TOKEN + ".cislo_objektu = " + downRelConnect.JoinEntryToColumn +
                        " and " + ClassTreeTable.JOIN_CURRENTALIAS_TOKEN + ".cislo_vztahu = " + this.RelationInfo.CisloVztahu.ToString() + ")";
                    table = this._AddTable(ClassTableType.None, "lcs.objektsubjekt", ClassTreeTableJoinType.LeftOuterJoin, joinDownExpression);
                }
                string joinMasterToColumn = table.GetAliasedColumn("cislo_subjektu");
                return new ClassTreeRelConnect(ClassTreeRelConnectType.Master, ClassTableType.None, null, joinMasterToColumn, null);
            }
            /// <summary>
            /// Vrátí napojovací údaje pro vztah 
            /// </summary>
            /// <param name="upClass"></param>
            /// <returns></returns>
            private ClassTreeRelConnect _GetRelationUpDynamicMasterToLeft(ClassTreeItem upClass)
            {
                // Dynamický vztah, v třídě Down má cílovou stranu (cislo_vztaz_subjektu), následuje tabulka dynamických vztahů vztahysubjektu,
                //  a do třídy Up se váže na její Master klíč, ze sloupce "cislo_subjektu" z dynamického vztahu.
                // Poznámka: pokud už v tomto prvku máme tabulku typu None, pak je vztah už připraven.
                ClassTreeTable table = this.FindTable(ClassTableType.None);
                if (table == null)
                {
                    ClassTreeRelConnect downRelConnect = new ClassTreeRelConnect(ClassTreeRelConnectType.Master);
                    this.LinkDown.FillCurrentKeyAlias(downRelConnect);
                    string joinDownExpression = "(" + ClassTreeTable.JOIN_CURRENTALIAS_TOKEN + ".cislo_vztaz_subjektu = " + downRelConnect.JoinMasterToColumn +
                        " and " + ClassTreeTable.JOIN_CURRENTALIAS_TOKEN + ".cislo_vztahu = " + this.RelationInfo.CisloVztahu.ToString() + ")";
                    table = this._AddTable(ClassTableType.None, "lcs.vztahysubjektu", ClassTreeTableJoinType.LeftOuterJoin, joinDownExpression);
                }
                string joinMasterToColumn = table.GetAliasedColumn("cislo_subjektu");
                return new ClassTreeRelConnect(ClassTreeRelConnectType.Master, ClassTableType.None, null, joinMasterToColumn, null);
            }
            /// <summary>
            /// Vrátí napojovací údaje pro vztah 
            /// </summary>
            /// <param name="upClass"></param>
            /// <returns></returns>
            private ClassTreeRelConnect _GetRelationUpDynamicEntryToLeft(ClassTreeItem upClass)
            {
                // Dynamický vztah, v třídě Down má cílovou stranu (cislo_subjektu), následuje tabulka dynamických vztahů objektsubjekt,
                //  a do třídy Up se váže na její Master a Entry klíč, ze sloupce "cislo_dokladu" a "cislo_objektu" z dynamického vztahu.
                // Poznámka: pokud už v tomto prvku máme tabulku typu None, pak je vztah už připraven.
                ClassTreeTable table = this.FindTable(ClassTableType.None);
                if (table == null)
                {
                    ClassTreeRelConnect downRelConnect = new ClassTreeRelConnect(ClassTreeRelConnectType.Master);
                    this.LinkDown.FillCurrentKeyAlias(downRelConnect);
                    string joinDownExpression = "(" + ClassTreeTable.JOIN_CURRENTALIAS_TOKEN + ".cislo_subjektu = " + downRelConnect.JoinMasterToColumn +
                        " and " + ClassTreeTable.JOIN_CURRENTALIAS_TOKEN + ".cislo_vztahu = " + this.RelationInfo.CisloVztahu.ToString() + ")";
                    table = this._AddTable(ClassTableType.None, "lcs.objektsubjekt", ClassTreeTableJoinType.LeftOuterJoin, joinDownExpression);
                }
                string joinMasterToColumn = table.GetAliasedColumn("cislo_dokladu");
                string joinEntryToColumn = table.GetAliasedColumn("cislo_objektu");
                return new ClassTreeRelConnect(ClassTreeRelConnectType.MasterEntry, ClassTableType.None, null, joinMasterToColumn, joinEntryToColumn);
            }
            /// <summary>
            /// Speciální metoda, dokáže získat aliasovaný sloupec (pro LinkUp = Attribute) na záznamu vztahu (this), 
            /// pokud na straně Up tohoto vztahu je Master a je to Subjekt, a hledaný sloupec odpovídá tabulce subjektů.
            /// </summary>
            /// <param name="tableType"></param>
            /// <param name="dbColumn"></param>
            /// <returns></returns>
            internal string GetColumnFromRelationAlias(ClassTableType tableType, string dbColumn)
            {
                this._CheckIsRelation("zpracovat alias tabulky třídy (Subject)");
                if (tableType != ClassTableType.Subjekt)
                    throw new ClassTreeException("Nelze vyhledat alias pro sloupec tabulky " + tableType.ToString() + ", pro prvek typu Relation lze zpracovat pouze subjekty.");
                if (!this.OwnerClassTree.IsColumnFromLcsSubjekty(dbColumn))
                    throw new ClassTreeException("Nelze vyhledat alias pro sloupec \"" + dbColumn + "\", sloupec se nevyskytuje v tabulce subjektů.");
                if (this.RelationSide != RelationSideType.Right)
                    throw new ClassTreeException("Nelze vyhledat alias pro sloupec \"" + dbColumn + "\", vztah (" + this.Text + ") nevede doprava na subjekt.");

                // Pokud dosud nemám subjekty joinované, napojíme je na vztah zespodu:
                ClassTreeTable table = this.FindTable(ClassTableType.Subjekt);
                if (table == null)
                {   // Vztah odspodu (přes tabulku vztahů pokud je třeba, tu zapojí automaticky):
                    ClassTreeRelConnect relUpConnect = this.GetRelationUp(null);

                    // Nyní bych měl zapojit tabulku subjektů na tabulku vztahů, anebo na spodní Master:
                    table = this._AddTable(ClassTableType.Subjekt, "lcs.subjekty", ClassTreeTableJoinType.LeftOuterJoin, ClassTreeTable.JOIN_CURRENTALIAS_TOKEN + ".cislo_subjektu = " + relUpConnect.JoinMasterToColumn);
                }
                return table.GetAliasedColumn(dbColumn);
            }
            /// <summary>
            /// Obsahuje (vrací) prefix tohoto vztahu, který se použije do aliasu atributu (před název sloupce) v režimu ColumnAutoAliasMode.RelationSuffix.
            /// </summary>
            public string RelationPrefixToColumnAlias
            {
                get
                {
                    if (!this.IsRelation) return null;
                    RelationInfo relInfo = this.RelationInfo;
                    string prefix = "";
                    string transName;
                    switch (this.CurrentRelationMode)
                    {
                        case RelationMode.StaticMasterToRight:
                        case RelationMode.StaticEntryToRight:
                            return relInfo.DbColumn.Trim() + "_";
                        case RelationMode.StaticMasterToLeft:
                        case RelationMode.StaticEntryToLeft:
                            return relInfo.DbColumn.Trim() + "s_";
                        case RelationMode.DynamicMasterToRight:
                        case RelationMode.DynamicEntryToRight:
                            transName = relInfo.TransformFromSide(RelationSideType.Left);
                            if (String.IsNullOrEmpty(transName))
                                transName = relInfo.TransformFromSide(RelationSideType.Right);
                            if (String.IsNullOrEmpty(transName))
                                return "r" + relInfo.CisloVztahu.ToString() + "_";
                            return transName.Trim() + "_";
                        case RelationMode.DynamicMasterToLeft:
                        case RelationMode.DynamicEntryToLeft:
                            transName = relInfo.TransformFromSide(RelationSideType.Right);
                            if (String.IsNullOrEmpty(transName))
                                transName = relInfo.TransformFromSide(RelationSideType.Left);
                            if (String.IsNullOrEmpty(transName))
                                return "l" + relInfo.CisloVztahu.ToString() + "_";
                            return transName.Trim() + "_";
                    }
                    return null;
                }
            }
            /// <summary>
            /// Obsahuje detailní druh vztahu v tomto prvku (statický / dynamický, doprava / doleva, master / entries)
            /// </summary>
            protected RelationMode CurrentRelationMode
            {
                get
                {
                    if (!this.IsRelation)
                        return RelationMode.None;
                    RelationInfo relationInfo = this.RelationInfo;
                    bool isEntry = relationInfo.IsEntry;

                    switch (relationInfo.RelationStatDyn)
                    {
                        case RelationStatDynType.Static:
                            switch (this.ItemType)
                            {
                                case ClassTreePathItemType.RelationLeftToRight:
                                    return (!isEntry ? RelationMode.StaticMasterToRight : RelationMode.StaticEntryToRight);
                                case ClassTreePathItemType.RelationRightToLeft:
                                    return (!isEntry ? RelationMode.StaticMasterToLeft : RelationMode.StaticEntryToLeft);
                            }
                            break;
                        case RelationStatDynType.Dynamic:
                            switch (this.ItemType)
                            {
                                case ClassTreePathItemType.RelationLeftToRight:
                                    return (!isEntry ? RelationMode.DynamicMasterToRight : RelationMode.DynamicEntryToRight);
                                case ClassTreePathItemType.RelationRightToLeft:
                                    return (!isEntry ? RelationMode.DynamicMasterToLeft : RelationMode.DynamicEntryToLeft);
                            }
                            break;
                    }
                    return RelationMode.None;
                }
            }
            /// <summary>
            /// Režim vztahu
            /// </summary>
            protected enum RelationMode
            {
                None,
                StaticMasterToRight,
                StaticEntryToRight,
                StaticMasterToLeft,
                StaticEntryToLeft,
                DynamicMasterToRight,
                DynamicEntryToRight,
                DynamicMasterToLeft,
                DynamicEntryToLeft
            }
            #endregion
            #endregion
            #region Property a metody pro roli IsAttribute
            /// <summary>
            /// Typ tabulky - pouze pro typ položky IsAttribute
            /// </summary>
            public ClassTableType AttributeTableType
            {
                get
                {
                    if (!this.IsAttribute)
                        return ClassTableType.None;

                    switch (this.ItemType)
                    {
                        case ClassTreePathItemType.AttributeSubject: return ClassTableType.Subjekt;
                        case ClassTreePathItemType.AttributeMaster: return ClassTableType.Master;
                        case ClassTreePathItemType.AttributeUdaMaster: return ClassTableType.UdaMaster;
                        case ClassTreePathItemType.AttributeEntries: return ClassTableType.Entries;
                        case ClassTreePathItemType.AttributeUdaEntries: return ClassTableType.UdaEntries;
                    }
                    return ClassTableType.None;
                }
            }
            /// <summary>
            /// Vrátí název sloupce v selectu, který obsahuje tímto atributem daná data.
            /// Lze použít pouze na prvku typu Attribute.
            /// </summary>
            /// <returns></returns>
            private string _GetAttributeColumnName()
            {
                this._CheckIsAttribute("získat název sloupce");
                if (this.LinkDown == null)
                    throw new ClassTreeException("Nelze získat název sloupce " + this.DbAttributeColumnName + ", podřízený prvek neexistuje.");
                if (this.LinkDown.IsClass)
                    return this.LinkDown.GetColumnAlias(this.AttributeTableType, this.DbAttributeColumnName);
                if (this.LinkDown.IsRelation)
                    return this.LinkDown.GetColumnFromRelationAlias(this.AttributeTableType, this.DbAttributeColumnName);
                throw new ClassTreeException("Nelze získat název sloupce " + this.DbAttributeColumnName + ", podřízený prvek je nevyhovující (" + this.LinkDown.Text + ").");
            }
            /// <summary>
            /// Vrátí alias pro tento sloupec, podle pravidel ClassTree.
            /// </summary>
            /// <returns></returns>
            private string _GetAttributeColumnAlias()
            {
                switch (this.OwnerClassTree.ColumnAutoAlias)
                {
                    case ColumnAutoAliasMode.None: return null;
                    case ColumnAutoAliasMode.Suffix: return this._GetAttributeColumnAliasSuffix();
                    case ColumnAutoAliasMode.RelationSuffix: return this._GetAttributeColumnAliasRelationSuffix();
                }
                return null;
            }
            /// <summary>
            /// Vrací alias sloupce v režimu Suffix
            /// </summary>
            /// <returns></returns>
            private string _GetAttributeColumnAliasSuffix()
            {
                // Určím, kolikátý atribut jsem já v seznamu atributů mého Ownera, z těch atributů které mají shodný _AttributeColumnAliasBasic:
                string alias = this._AttributeColumnAliasBasic;
                int index = this.OwnerClassTree.FinalList
                    .Where(item => String.Equals(item._AttributeColumnAliasBasic, alias, StringComparison.InvariantCultureIgnoreCase))
                    .ToList()
                    .FindIndex(item => Object.ReferenceEquals(item, this));

                if (index <= 0) return alias;
                return alias + "_" + index.ToString();
            }
            /// <summary>
            /// Vrací alias sloupce v režimu RelationSuffix
            /// </summary>
            /// <returns></returns>
            private string _GetAttributeColumnAliasRelationSuffix()
            {
                // Určím, kolikátý atribut jsem já v seznamu atributů mého Ownera, z těch atributů které mají shodný _AttributeColumnAliasBasic:
                string alias = this._AttributeColumnAliasRelation;
                int index = this.OwnerClassTree.FinalList
                    .Where(item => String.Equals(item._AttributeColumnAliasRelation, alias, StringComparison.InvariantCultureIgnoreCase))
                    .ToList()
                    .FindIndex(item => Object.ReferenceEquals(item, this));

                if (index <= 0) return alias;
                return alias + "_" + index.ToString();
            }
            /// <summary>
            /// Obsahuje alias sloupce bez aliasu tabulky, bez prefixu a suffixu
            /// </summary>
            private string _AttributeColumnAliasBasic { get { return this.DbAttributeColumnName; } }
            /// <summary>
            /// Obsahuje alias sloupce bez aliasu tabulky, s prefixem vztahu, bez suffixu
            /// </summary>
            private string _AttributeColumnAliasRelation
            {
                get
                {
                    if (this.__AttributeColumnAliasRelation == null)
                    {   // Získání tohoto textu obnáší trochu práce, a používá se často, takže mi stojí za to si jej pamatovat (nemění se):
                        ClassTreeItem relation = this.FindDown(item => item.IsRelation);
                        if (relation == null)
                            // Pokud ve směru dolů není žádný vztah, pak bude alias bez prefixu:
                            this.__AttributeColumnAliasRelation = this.DbAttributeColumnName;
                        else
                            this.__AttributeColumnAliasRelation = relation.RelationPrefixToColumnAlias + this.DbAttributeColumnName;
                    }
                    return this.__AttributeColumnAliasRelation;
                }
            }
            /// <summary>
            /// Metoda najde nejbližší nižší prvek, který vyhovuje dané podmínce.
            /// Prvek this do toho nespadá.
            /// Vrátí první prvek vyhovující, anebo null když nenajde nic.
            /// </summary>
            /// <param name="predicate"></param>
            /// <returns></returns>
            public ClassTreeItem FindDown(Func<ClassTreeItem, bool> predicate)
            {
                ClassTreeItem findItem = this.LinkDown;
                while (findItem != null)
                {
                    if (predicate(findItem)) return findItem;
                    findItem = findItem.LinkDown;
                }
                return null;
            }
            private string __AttributeColumnAliasRelation;
            #endregion
            #region Seznam použitých tabulek, vyhledání podle typu tabulky, fyzické přidání tabulky
            /// <summary>
            /// Vrátí tabulku daného typu ze seznamu tabulek již zapojených.
            /// </summary>
            /// <param name="types">Seznam hledaných typů tabulek. Vrátí se použitá tabulka, jejíž typ je uveden na nejprvnějších místech seznamu.</param>
            /// <returns></returns>
            internal ClassTreeTable FindTable(params ClassTableType[] types)
            {
                if (this.TableList.Count > 0)
                {
                    foreach (ClassTableType type in types)
                    {
                        ClassTreeTable table;
                        if (this.TableDict.TryGetValue(type, out table))
                            return table;
                    }
                }
                return null;
            }
            /// <summary>
            /// Zjistí, zda tento prvek obsahuje ve svých použitých tabulkách tabulku některého z daných typů.
            /// </summary>
            /// <param name="types"></param>
            /// <returns></returns>
            internal bool ContainTable(params ClassTableType[] types)
            {
                return (this.FindTable(types) != null);
            }
            /// <summary>
            /// Přidá do this.Tables novou tabulku podle dané specifikace. Alias určí automaticky.
            /// </summary>
            /// <param name="tableType"></param>
            /// <param name="tableName"></param>
            /// <param name="tableJoin"></param>
            /// <param name="joinExpression"></param>
            /// <returns></returns>
            private ClassTreeTable _AddTable(ClassTableType tableType, string tableName, ClassTreeTableJoinType tableJoin, string joinExpression)
            {
                string tableAlias = this.OwnerClassTree.CreateAlias(this.Key, tableType);
                ClassTreeTable table = new ClassTreeTable(tableType, tableName, tableAlias, tableJoin, joinExpression);
                this.TableList.Add(table);
                this.TableDict.Add(tableType, table);
                return table;
            }
            /// <summary>
            /// Přidá do this.Tables novou tabulku podle dané specifikace. Alias je předán.
            /// </summary>
            /// <param name="tableType"></param>
            /// <param name="tableName"></param>
            /// <param name="tableAlias"></param>
            /// <returns></returns>
            private ClassTreeTable _AddTable(ClassTableType tableType, string tableName, string tableAlias)
            {
                ClassTreeTable table = new ClassTreeTable(tableType, tableName, tableAlias, ClassTreeTableJoinType.External, null);
                this.TableList.Add(table);
                this.TableDict.Add(tableType, table);
                return table;
            }
            /// <summary>
            /// Klíčová metoda, na prvku typu Class.
            /// Metoda najde požadovanou tabulku této třídy (v případě potřeby zajistí její přidání do this a this do stromu tříd), a vrátí aliasovaný požadovaný sloupec.
            /// Metoda nekontroluje, zda požadovaný sloupec existuje v požadované tabulce - to je věcí volajícího.
            /// Tato metoda je povolena pouze pro prvek typu Class.
            /// </summary>
            /// <param name="tableName"></param>
            /// <param name="dbColumn"></param>
            /// <returns></returns>
            internal string GetColumnAlias(string tableName, string dbColumn)
            {
                this._CheckIsClass("zpracovat alias tabulky třídy (" + tableName + ")");
                ClassTableType tableType = this.ClassInfo.GetTableType(tableName);
                if (tableType == ClassTableType.None)
                    throw new ClassTreeException("Nelze vyhledat tabulku " + tableName + " (" + tableType.ToString() + "), tabulka ve třídě " + this.ClassInfo.ToString() + " neexistuje.");
                return this._GetColumnAlias(tableType, dbColumn);
            }
            /// <summary>
            /// Klíčová metoda, na prvku typu Class.
            /// Metoda najde požadovanou tabulku této třídy (v případě potřeby zajistí její přidání do this a this do stromu tříd), a vrátí aliasovaný požadovaný sloupec.
            /// Metoda nekontroluje, zda požadovaný sloupec existuje v požadované tabulce - to je věcí volajícího.
            /// Tato metoda je povolena pouze pro prvek typu Class.
            /// </summary>
            /// <param name="tableType"></param>
            /// <param name="dbColumn"></param>
            /// <returns></returns>
            internal string GetColumnAlias(ClassTableType tableType, string dbColumn)
            {
                this._CheckIsClass("zpracovat alias tabulky třídy (" + tableType.ToString() + ")");
                return this._GetColumnAlias(tableType, dbColumn);
            }
            /// <summary>
            /// Vrátí alias daného sloupce dané tabulky, tabulku v případě potřeby zařadí do stromu.
            /// </summary>
            /// <param name="tableType"></param>
            /// <param name="column"></param>
            /// <returns></returns>
            private string _GetColumnAlias(ClassTableType tableType, string column)
            {
                if (!this.ClassInfo.ContainTable(tableType))
                    throw new ClassTreeException("Nelze vyhledat tabulku typu " + tableType.ToString() + " (" + tableType.ToString() + "), tabulka ve třídě " + this.ClassInfo.ToString() + " neexistuje.");

                ClassTreeTable table = this.FindTable(tableType);
                if (table == null)
                    table = this.FindAlternateTable(tableType, column);
                if (table == null)
                    table = this.AddJoinTable(tableType);

                // dbColumn může být Computed column (computed na úrovni Greenu!):
                string dbColumn = column;
                NorisAttributesCls norisAttribute = this.ClassInfo.FindAttribute(column, tableType);
                if (norisAttribute != null && norisAttribute.IsExpression)
                {
                    dbColumn = norisAttribute.ExpressionAliased(this._GetTableAlias);
                    return "(" + dbColumn + ")";
                }

                return table.GetAliasedColumn(dbColumn);
            }
            /// <summary>
            /// Metoda najde danou tabulku ve svých použitých tabulkách, a vrátí její alias.
            /// Pokud tabulku nemáme použitou, vrací se null.
            /// Pokud tabulka nemá přidělen alias, vrací se název tabulky.
            /// </summary>
            /// <param name="tableName"></param>
            /// <returns></returns>
            private string _GetTableAlias(string tableName)
            {
                ClassTreeTable table = this.TableList.FirstOrDefault(t => String.Equals(t.TableName, tableName, StringComparison.InvariantCultureIgnoreCase));
                if (table == null) return null;
                return table.CurrentAlias;
            }
            /// <summary>
            /// Metoda zkusí najít alternativní tabulku (která by byla v this již použita) k dané tabulce, pro daný sloupec.
            /// Proč: některé sloupce jsou "Systémové atributy a vztahy", které jsou kopírované z tabulky Subjekt do tabulky Master.
            /// Pokud je požadován takový sloupec, z tabulky která ještě není zařazena do selectu, 
            /// a přitom v selectu máme zařazenou komplementární tabulku, a třída kopíruje systémové atributy,
            /// pak můžeme vrátit tabulku komplementární. Požadovaný sloupec se načte z alternativní tabulky.
            /// </summary>
            /// <param name="tableType"></param>
            /// <param name="dbColumn"></param>
            /// <returns></returns>
            private ClassTreeTable FindAlternateTable(ClassTableType tableType, string dbColumn)
            {
                ClassTableType alternativeTableType = this.GetAlternativeTableType(tableType, dbColumn);
                if (alternativeTableType == ClassTableType.None) return null;
                return this.FindTable(alternativeTableType);
            }
            /// <summary>
            /// Vrátí typ alternativní tabulky pro původní typ tabulky a její db sloupec
            /// </summary>
            /// <param name="tableType"></param>
            /// <param name="dbColumn"></param>
            /// <returns></returns>
            private ClassTableType GetAlternativeTableType(ClassTableType tableType, string dbColumn)
            {
                if (!this.ClassInfo.IsSubject) return ClassTableType.None;         // Nonsubjektové třídy nekopírují systémové atributy
                if (tableType == ClassTableType.Entries || tableType == ClassTableType.UdaMaster || tableType == ClassTableType.UdaEntries) return ClassTableType.None;

                // Alternativní typ je: k typu Subjekt => Master, k typu Master => Subjekt:
                ClassTableType alternativeType = (tableType == ClassTableType.Subjekt ? ClassTableType.Master :
                                                 (tableType == ClassTableType.Master ? ClassTableType.Subjekt : ClassTableType.None));
                if (alternativeType == ClassTableType.None) return ClassTableType.None;
                if (!this.ContainTable(alternativeType)) return ClassTableType.None;

                string column = dbColumn.Trim().ToLower();

                // Načtu informace o původním sloupci:
                DbColumnInfo columnInfo;
                DbTableInfo tableInfo = this.ClassInfo.GetTable(tableType);        // Popis vlastností tabulky původního typu
                if (tableInfo == null)
                    return ClassTableType.None;
                if (!tableInfo.TryFindColumn(column, out columnInfo))
                    return ClassTableType.None;

                // Pouze sloupce z tabulky subjektů se mohou kopírovat, pro jiné rovnou vrátím None:
                if (this.OwnerClassTree.IsColumnFromLcsSubjekty(column)) return ClassTableType.None;

                // Načtu informace o alternativním sloupci:
                DbColumnInfo alternativeColumnInfo;
                DbTableInfo alternativeTableInfo = this.ClassInfo.GetTable(alternativeType);
                if (alternativeTableInfo == null)
                    return ClassTableType.None;
                if (!alternativeTableInfo.TryFindColumn(column, out alternativeColumnInfo))
                    return ClassTableType.None;

                // Alternativní tabulku vrátím jen tehdy, když alternativní sloupec má shodné (nebo lepší?) datové vlastnosti jako sloupec původní:
                if (!(alternativeColumnInfo.TypeName == columnInfo.TypeName &&
                      alternativeColumnInfo.ColumnLength >= columnInfo.ColumnLength &&
                      alternativeColumnInfo.ColumnPrecision >= columnInfo.ColumnPrecision))
                    return ClassTableType.None;

                // OK:
                return alternativeType;
            }
            /// <summary>
            /// Vrátí všechny použité tabulky.
            /// Vrací je jako texty do SQL příkazu FROM, tedy každá tabulka začíná textem "FROM" nebo "JOIN" nebo "LEFT OUTER JOIN",
            /// následuje název a alias tabulky a případně výraz ON pro JOINy.
            /// Na konci je komentář popisující původ tabulky a joinu.
            /// Volitelně lze vyžádat i ze všech Up prvků.
            /// </summary>
            /// <returns></returns>
            internal List<string> GetAddedTables(bool scanUp)
            {
                List<ClassTreeTable> result = new List<ClassTreeTable>();
                this._FillAddedTables(scanUp, result);
                return result.ConvertAll(t => t.SqlText);
            }
            /// <summary>
            /// Do resultu přidá všechny svoje použité tabulky (jako objekty třídy ClassTreeTable).
            /// Volitelně i ze všech Up prvků.
            /// Nepoužívá rekurzi, takže nejde "jeden prvek do celé hloubky", 
            /// používá frontu (Queue) budoucích prvků tvořenou do šířky, takže bere prvky ve stromu po jejich hladinách.
            /// Uvnitř jednoho prvku bere tabulky chronologicky tak, jak byly přidávány (což odpovídá konstrukci JOINů).
            /// </summary>
            /// <param name="scanUp"></param>
            /// <param name="result"></param>
            private void _FillAddedTables(bool scanUp, List<ClassTreeTable> result)
            {
                Queue<ClassTreeItem> queue = new Queue<ClassTreeItem>();
                queue.Enqueue(this);
                while (queue.Count > 0)
                {
                    ClassTreeItem item = queue.Dequeue();
                    if (item.HasAddTables)
                        result.AddRange(item.AddTables);
                    if (scanUp && item.LinkUpList != null)
                    {
                        foreach (ClassTreeItem upItem in item.LinkUpList)
                            queue.Enqueue(upItem);
                    }
                }
            }
            /// <summary>
            /// Tabulky použité v tomto prvku (tabulky mohou být použity v prvku typu Třída, nebo Vztah pokud je dynamický).
            /// Tabulky jsou v seznamu ukládány chronologicky, jak byly přidávány.
            /// </summary>
            internal List<ClassTreeTable> TableList
            {
                get
                {
                    if (this._TableList == null)
                        this._TableList = new List<ClassTreeTable>();
                    return this._TableList;
                }
            }
            /// <summary>
            /// Index tabulek podle jejich typu.
            /// V jednom prvku nemohou být použity dvě tabulky stejného typu.
            /// </summary>
            internal Dictionary<ClassTableType, ClassTreeTable> TableDict
            {
                get
                {
                    if (this._TableDict == null)
                        this._TableDict = new Dictionary<ClassTableType, ClassTreeTable>();
                    return this._TableDict;
                }
            }
            /// <summary>
            /// V tomto prvku jsou nějaké tabulky?
            /// Zdejší property signalizuje jakoukoli tabulku tohoto prvku, včetně tabulek použitých z FROM fráze.
            /// Tabulky z FROM fráze se používají při tvorbě aliasu zdejšícj sloupců, ale nevydávají se ven v metodě GetAllTables().
            /// Metoda GetAllTables() vydává pouze ty tabulky, které tento prvek přidal sám z důvodu vlastní potřeby.
            /// </summary>
            internal bool HasTables { get { return (this._TableList != null && this._TableList.Count > 0); } }
            /// <summary>
            /// V tomto prvku jsou nějaké tabulky?
            /// Zdejší property signalizuje jakoukoli tabulku tohoto prvku, včetně tabulek použitých z FROM fráze.
            /// Tabulky z FROM fráze se používají při tvorbě aliasu zdejšícj sloupců, ale nevydávají se ven v metodě GetAllTables().
            /// Metoda GetAllTables() vydává pouze ty tabulky, které tento prvek přidal sám z důvodu vlastní potřeby.
            /// </summary>
            internal bool HasAddTables { get { return (this._TableList != null && this._TableList.Any(t => t.IsAddInClassTree)); } }
            /// <summary>
            /// Tabulky přidané tímto prvkem nad rámec fráze FROM.
            /// Tyto tabulky se musí do fráze FROM přidat, aby celkový dotaz obsahoval potřebné tabulky (zdroje dat) pro výrazy (pole), 
            /// o které byl ClassTree požádán.
            /// </summary>
            internal IEnumerable<ClassTreeTable> AddTables
            {
                get
                {
                    if (this._TableList == null) return null;
                    return this._TableList.Where(t => t.IsAddInClassTree);
                }
            }
            /// <summary>
            /// Tabulky
            /// </summary>
            private List<ClassTreeTable> _TableList;
            /// <summary>
            /// Index tabulek podle typu
            /// </summary>
            private Dictionary<ClassTableType, ClassTreeTable> _TableDict;
            #endregion
            #region IClassTreeItem Members
            /// <summary>
            /// Obsahuje (vytvoří a vrátí) název sloupce včetně aliasu zdrojové tabulky, který představuje zdejší atribut.
            /// </summary>
            public string AttributeAliasedColumn
            {
                get { return this._GetAttributeColumnName(); }
            }
            /// <summary>
            /// Obsahuje (vytvoří a vrátí) alias pro tento sloupec, podle pravidel ClassTree.
            /// </summary>
            public string AttributeColumnAlias
            {
                get { return this._GetAttributeColumnAlias(); }
            }
            string IClassTreeColumn.Column { get { return this.AttributeAliasedColumn; } }
            string IClassTreeColumn.Alias { get { return this.AttributeColumnAlias; } }
            #endregion
        }
        #endregion
        #region class ClassTreeTable : jedna tabulka použitá v kterémkoli prvku ClassTreeItem
        /// <summary>
        /// ClassTreeTable : jedna tabulka použitá v kterémkoli prvku ClassTreeItem
        /// </summary>
        protected class ClassTreeTable
        {
            /// <summary>
            /// Konstruktor
            /// </summary>
            /// <param name="type"></param>
            /// <param name="name"></param>
            /// <param name="alias"></param>
            /// <param name="joinType"></param>
            /// <param name="joinExpression"></param>
            public ClassTreeTable(ClassTableType type, string name, string alias, ClassTreeTableJoinType joinType, string joinExpression)
            {
                this.TableType = type;
                this.TableName = name;
                this.TableAlias = alias;
                this.TableJoinType = joinType;
                this.TableJoinExpression = joinExpression;
            }
            /// <summary>
            /// Vizualizace
            /// </summary>
            /// <returns></returns>
            public override string ToString()
            {
                return this.SqlText;
            }
            /// <summary>
            /// Typ tabulky.
            /// Pozor, v prvku který je Relation a Dynamic se používají vztahové tabulky, a v typu tabulky mají None.
            /// </summary>
            public ClassTableType TableType { get; private set; }
            /// <summary>
            /// Název tabulky včetně vlastníka
            /// </summary>
            public string TableName { get; private set; }
            /// <summary>
            /// Alias tabulky v rámci stromu (ClassTree).
            /// </summary>
            public string TableAlias { get; private set; }
            /// <summary>
            /// Druh joinu této tabulky
            /// </summary>
            public ClassTreeTableJoinType TableJoinType { get; private set; }
            /// <summary>
            /// Obsahuje true, pokud tato tabulka je přidaná metodami ClassTree nad rámec předané FROM fráze.
            /// </summary>
            public bool IsAddInClassTree
            { get { return (this.TableJoinType == ClassTreeTableJoinType.From || this.TableJoinType == ClassTreeTableJoinType.Join || this.TableJoinType == ClassTreeTableJoinType.LeftOuterJoin); } }
            /// <summary>
            /// Obsahuje true, pokud tato tabulka je přidaná proto, že byla nalezena ve frázi FROM.
            /// </summary>
            public bool IsExternal
            { get { return (this.TableJoinType == ClassTreeTableJoinType.External); } }
            /// <summary>
            /// Zadaný výraz pro joinování (za slovem ON). Zdejší výraz může obsahovat token JOIN_CURRENTALIAS_TOKEN, na jehož místo bude doplněn zdejší alias, viz property this.CurrentJoinExpression
            /// </summary>
            public string TableJoinExpression { get; private set; }
            /// <summary>
            /// Aktuální výraz pro joinování (za slovem ON).
            /// POkud byl zadán výraz s tokenem JOIN_CURRENTALIAS_TOKEN, pak zde je nahrazen aktuálním aliasem (CurrentAlias).
            /// </summary>
            public string CurrentJoinExpression
            {
                get
                {
                    string expression = this.TableJoinExpression;
                    if (!String.IsNullOrEmpty(expression) && expression.Contains(JOIN_CURRENTALIAS_TOKEN))
                        expression = expression.Replace(JOIN_CURRENTALIAS_TOKEN, this.CurrentAlias);
                    return expression;
                }
            }
            /// <summary>
            /// Aktuální alias. Pokud je zadán TableAlias, vrací se ten. Jinak se vrací TableName.
            /// </summary>
            public string CurrentAlias
            {
                get { return (!String.IsNullOrEmpty(this.TableAlias) ? this.TableAlias : this.TableName).Trim(); }
            }
            /// <summary>
            /// Vrátí název sloupce doplněný o alias této tabulky
            /// </summary>
            /// <param name="dbColumn"></param>
            /// <returns></returns>
            internal string GetAliasedColumn(string dbColumn)
            {
                return this.CurrentAlias.Trim() + "." + dbColumn.Trim();
            }
            /// <summary>
            /// Token, který může být použit v parametru joinExpression
            /// </summary>
            public const string JOIN_CURRENTALIAS_TOKEN = "{{CurrentAlias}}";
            /// <summary>
            /// Zjistí, zda tato tabulka vyhovuje některému z daných typů.
            /// Vrací index toho typu, který odpovídá této tabulce.
            /// Vrací -1, pokud nevyhovuje žádnému. Vrací -2, pokud types je null.
            /// </summary>
            /// <param name="types"></param>
            /// <returns></returns>
            public int CorrespondingToType(ClassTableType[] types)
            {
                if (types == null) return -2;
                for (int i = 0; i < types.Length; i++)
                {
                    if (this.TableType == types[i])
                        return i;
                }
                return -1;
            }
            /// <summary>
            /// Text tabulky: "FROM tablename alias ", "LEFT OUTER JOIN tablename alias ON (alias.column = joincolumn ...)".
            /// </summary>
            public string SqlText
            {
                get
                {
                    string sqlText = "";
                    switch (this.TableJoinType)
                    {
                        case ClassTreeTableJoinType.From:
                            sqlText += "FROM ";
                            break;
                        case ClassTreeTableJoinType.Join:
                            sqlText += " JOIN ";
                            break;
                        case ClassTreeTableJoinType.LeftOuterJoin:
                            sqlText += " LEFT OUTER JOIN ";
                            break;
                    }
                    sqlText += this.TableName + " ";
                    if (!String.IsNullOrEmpty(this.TableAlias))
                        sqlText += this.TableAlias + " ";
                    string joinExpression =  this.CurrentJoinExpression;
                    if ((this.TableJoinType == ClassTreeTableJoinType.Join || this.TableJoinType == ClassTreeTableJoinType.LeftOuterJoin)
                         && !String.IsNullOrEmpty(joinExpression))
                        sqlText += "ON " + joinExpression + " ";
                    return sqlText;
                }
            }
        }
        #endregion
        #region class ClassTreeRelConnect : popis vzájemného napojení vztahu a třídy (sloupce do výrazu JOIN)
        /// <summary>
        /// ClassTreeRelConnect : popis napojení vztahu a třídy (sloupce do výrazu JOIN)
        /// </summary>
        protected class ClassTreeRelConnect
        {
            public ClassTreeRelConnect(ClassTreeRelConnectType connectType)
            {
                this.ConnectType = connectType;
                this.ConnectTableType = ClassTableType.None;
                this.ConnectDbColumn = null;
                this.JoinMasterToColumn = null;
                this.JoinEntryToColumn = null;
            }
            public ClassTreeRelConnect(ClassTreeRelConnectType connectType, ClassTableType tableType, string connectDbColumn, string joinMasterToColumn, string joinEntryToColumn)
            {
                this.ConnectType = connectType;
                this.ConnectTableType = tableType;
                this.ConnectDbColumn = connectDbColumn;
                this.JoinMasterToColumn = joinMasterToColumn;
                this.JoinEntryToColumn = joinEntryToColumn;
            }
            /// <summary>
            /// Režim napojení vztahu (klíč záznamu, klíč záznamu a položky, statický vztah z tabulky typu ConnectTableType)
            /// </summary>
            public ClassTreeRelConnectType ConnectType { get; private set; }
            /// <summary>
            /// Typ tabulky, jejíž konkrétní sloupec (ConnectDbColumn) se má napojit do joinu na sloupec this.JoinMasterToColumn.
            /// Tato tabulka se používá při vztahu zprava doleva, když blíže k Root je typicky číselník, a na pozici Up je doklad,
            /// který se joinuje svým statickým vztahem na onen číselník. 
            /// Sloupec statického vztahu je definován: tabulka = ConnectType, sloupec tabulky = ConnectDbColumn.
            /// Tento vztah se provádí tehdy, když (this.ConnectType == ClassTreeRelConnectType.Column).
            /// </summary>
            public ClassTableType ConnectTableType { get; private set; }
            /// <summary>
            /// Sloupec statického vztahu pro napojení na this.JoinMasterToColumn, v případě když (this.ConnectType == ClassTreeRelConnectType.Column).
            /// Více u property this.ConnectTableType
            /// </summary>
            public string ConnectDbColumn { get; private set; }
            /// <summary>
            /// Sloupec včetně aliasu pocházející odněkud ze směru Down, na který se joinuje zdejší Master.
            /// </summary>
            public string JoinMasterToColumn { get; set; }
            /// <summary>
            /// Sloupec včetně aliasu pocházející odněkud ze směru Down, na který se joinuje zdejší Entry key, 
            /// pouze v případě když (this.ConnectType == ClassTreeRelConnectType.MasterEntry).
            /// </summary>
            public string JoinEntryToColumn { get; set; }
        }
        #endregion
        #region class ClassTreeException : systémová chyba v ClassTree
        /// <summary>
        /// ClassTreeException : systémová chyba v ClassTree.
        /// Používá se tato pro případné odlišení důvodu chyby.
        /// </summary>
        public class ClassTreeException : SysException
        {
            public ClassTreeException() : base() { }
            public ClassTreeException(string message) : base(message) { }
            public ClassTreeException(string message, Exception inner) : base(message, inner) { }
        }
        #endregion
        #region enum ClassTreePathItemType, ClassTreeTableJoinType, ClassTreeRelConnectType, ColumnAutoAliasMode
        /// <summary>
        /// ClassTreePathItemType : druhy položek v jedné položce větve stromu tříd (v ClassTreeItem)
        /// </summary>
        protected enum ClassTreePathItemType
        {
            /// <summary>Žádný obsah</summary>
            None = 0,
            /// <summary>Třída (nikoli atribut)</summary>
            Class,
            /// <summary>Vztah zleva doprava, typicky doubleclick na podtrženém políčku formuláře = směr statického vztahu zleva z db sloupce, doprava na číslo záznamu číselníku</summary>
            RelationLeftToRight,
            /// <summary>Vztah zprava doleva, typicky dolní záložka = všechny záznamy, které svým db sloupcem ukazují na konkrétní záznam číselníku</summary>
            RelationRightToLeft,
            /// <summary>Atribut ze subjektů = z netypového vztahu</summary>
            AttributeSubject,
            /// <summary>Atribut z tabulky Master</summary>
            AttributeMaster,
            /// <summary>Atribut z tabulky Entries</summary>
            AttributeEntries,
            /// <summary>Atribut z tabulky UDA Master</summary>
            AttributeUdaMaster,
            /// <summary>Atribut z tabulky UDA Entries</summary>
            AttributeUdaEntries
        }
        /// <summary>
        /// Typy zapojení tabulky do stávající fráze FROM
        /// </summary>
        protected enum ClassTreeTableJoinType
        {
            None = 0,
            From,
            Join,
            LeftOuterJoin,
            External
        }
        /// <summary>
        /// Druh vazby mezi vztahem a třídou
        /// </summary>
        protected enum ClassTreeRelConnectType
        {
            /// <summary>
            /// Neurčeno
            /// </summary>
            None = 0,
            /// <summary>
            /// Třída (Up) má navázat svůj Master klíč na sloupec ClassTreeRelConnect.JoinMasterToColumn 
            /// (který obsahuje alias a název sloupce z některé Down tabulky).
            /// </summary>
            Master,
            /// <summary>
            /// Třída (Up) má navázat svůj Master klíč na sloupec ClassTreeRelConnect.JoinMasterToColumn,
            /// a svůj Entry klíč na sloupec ClassTreeRelConnect.JoinEntryToColumn 
            /// (oba klíče obsahují alias a název sloupce z některé Down tabulky).
            /// </summary>
            MasterEntry,
            /// <summary>
            /// Třída (Up) má navázat svůj specifický sloupec 
            /// (tabulka = ClassTreeRelConnect.ConnectType, sloupec tabulky = ClassTreeRelConnect.ConnectDbColumn)
            /// </summary>
            Column
        }
        /// <summary>
        /// Automatická tvorba aliasů pro sloupce (atributy ve stromu tříd)
        /// </summary>
        public enum ColumnAutoAliasMode
        {
            /// <summary>
            /// Ke sloupcům nedávat alias, hrozí pak duplicita názvů sloupců v selectu.
            /// </summary>
            None = 0,
            /// <summary>
            /// Ke sloupcům přidávat alias jen tehdy, když se v seznamu sloupců vyskytne sloupec s názvem, který už byl. Doplnit suffix "_1", "_2". atd.
            /// </summary>
            Suffix,
            /// <summary>
            /// Ke sloupcům přidávat alias vždy, a to: prefix dle názvu nejbližšího vztahu ve směru Down (pokud se najde), a přidávat suffix v případě duplicity.
            /// </summary>
            RelationSuffix
        }
        #endregion
        #region Konstanty a konvertory
        /// <summary>':': Separátor prvků v FullTreePath</summary>
        public const char TREEPATH_SEP_ITEM = ':';
        /// <summary>'&gt;': Separátor názvu druhu tabulky v atributu v FullTreePath</summary>
        public const char TREEPATH_SEP_ATTR_TABLE = '>';
        /// <summary>'.': Separátor názvu db sloupce v atributu v FullTreePath</summary>
        public const char TREEPATH_SEP_ATTR_COLUMN = '.';
        /// <summary>'C': prefix čísla třídy ve FullTreePath</summary>
        public const char TREEPATH_CLASS = 'C';
        /// <summary>'R': prefix čísla vztahu vedoucího zleva doprava ve FullTreePath</summary>
        public const char TREEPATH_REL_LEFT_TO_RIGHT = 'R';
        /// <summary>'L': prefix čísla vztahu vedoucího zprava doleva ve FullTreePath</summary>
        public const char TREEPATH_REL_RIGHT_TO_LEFT = 'L';
        /// <summary>"Subjekt": prefix atributu pocházejícího z tabulky subjektů</summary>
        public const string TREEPATH_TABLE_SUBJECT = "Subjekt";
        /// <summary>"Master": prefix atributu pocházejícího z tabulky Master</summary>
        public const string TREEPATH_TABLE_MASTER = "Master";
        /// <summary>"RecordNumber": prefix atributu reprezentujícího číslo záznamu</summary>
        public const string TREEPATH_TABLE_RECORDNUMBER = "RecordNumber";
        /// <summary>"MasterUda": prefix atributu pocházejícího z tabulky UDA Master</summary>
        public const string TREEPATH_TABLE_MASTER_UDA = "MasterUda";
        /// <summary>"Entries": prefix atributu pocházejícího z tabulky Entries</summary>
        public const string TREEPATH_TABLE_ENTRIES = "Entries";
        /// <summary>"EntriesUda": prefix atributu pocházejícího z tabulky UDA Entries</summary>
        public const string TREEPATH_TABLE_ENTRIES_UDA = "EntriesUda";
        /// <summary>
        /// Vrací konstantu (string) pro daný typ tabulky
        /// </summary>
        /// <param name="tableType"></param>
        /// <returns></returns>
        public static string ConvertTableType(ClassTableType tableType)
        {
            switch (tableType)
            {
                case ClassTableType.Subjekt: return TREEPATH_TABLE_SUBJECT;
                case ClassTableType.Master: return TREEPATH_TABLE_MASTER;
                case ClassTableType.Entries: return TREEPATH_TABLE_ENTRIES;
                case ClassTableType.UdaMaster: return TREEPATH_TABLE_MASTER_UDA;
                case ClassTableType.UdaEntries: return TREEPATH_TABLE_ENTRIES_UDA;
            }
            return null;
        }
        /// <summary>
        /// Vrací typ tabulky pro danou konstantu (string)
        /// </summary>
        /// <param name="table"></param>
        /// <returns></returns>
        public static ClassTableType ConvertTableType(string table)
        {
            if (String.IsNullOrEmpty(table)) return ClassTableType.None;
            switch (table)
            {
                case TREEPATH_TABLE_SUBJECT: return ClassTableType.Subjekt;
                case TREEPATH_TABLE_MASTER: return ClassTableType.Master;
                case TREEPATH_TABLE_ENTRIES: return ClassTableType.Entries;
                case TREEPATH_TABLE_MASTER_UDA: return ClassTableType.UdaMaster;
                case TREEPATH_TABLE_ENTRIES_UDA: return ClassTableType.UdaEntries;
            }
            return ClassTableType.None;
        }
        #endregion
    }
    #region interface IClassTreeColumn : deklaruje přítomnost property Column. Dovoluje tak vydávat ven i objekt, který je protected - když se na něj hledí jako na interface, tak to lze.
    /// <summary>
    /// Interface, který deklaruje přítomnost property Column.
    /// </summary>
    public interface IClassTreeColumn
    {
        /// <summary>
        /// Sloupec v selectu, reprezentující požadovaná data.
        /// </summary>
        string Column { get; }
        /// <summary>
        /// Alias sloupce v selectu, podle preferencí nastavených v ClassTree
        /// </summary>
        string Alias { get; }
    }
    #endregion
}
